diff mbox

[03/10] watchdog: Add support for PTXPMB CPLD watchdog

Message ID 1475853451-22121-4-git-send-email-pantelis.antoniou@konsulko.com
State Not Applicable
Headers show

Commit Message

Pantelis Antoniou Oct. 7, 2016, 3:17 p.m. UTC
From: Guenter Roeck <groeck@juniper.net>

Add support for Junipers PTXPMB CPLD's watchdog device.

Signed-off-by: Guenter Roeck <groeck@juniper.net>
[Ported from Juniper kernel]
Signed-off-by: Pantelis Antoniou <pantelis.antoniou@konsulko.com>
---
 drivers/watchdog/Kconfig      |  12 ++
 drivers/watchdog/Makefile     |   1 +
 drivers/watchdog/ptxpmb_wdt.c | 283 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 296 insertions(+)
 create mode 100644 drivers/watchdog/ptxpmb_wdt.c
diff mbox

Patch

diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 50dbaa8..2554f47 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -131,6 +131,18 @@  config GPIO_WATCHDOG_ARCH_INITCALL
 	  arch_initcall.
 	  If in doubt, say N.
 
+config JNX_PTXPMB_WDT
+	tristate "Juniper PTX PMB CPLD watchdog"
+	depends on MFD_JUNIPER_CPLD
+	default y if MFD_JUNIPER_CPLD
+	select WATCHDOG_CORE
+	help
+	  Watchdog timer embedded into the boot CPLD on PTX PMB on
+	  relevant Juniper platforms.
+
+	  This driver can also be built as a module.  If so, the module
+	  will be called jnx-ptxpmb-wdt.
+
 config MENF21BMC_WATCHDOG
 	tristate "MEN 14F021P00 BMC Watchdog"
 	depends on MFD_MENF21BMC
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index cba0043..942b795 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -110,6 +110,7 @@  obj-$(CONFIG_ITCO_WDT) += iTCO_vendor_support.o
 endif
 obj-$(CONFIG_IT8712F_WDT) += it8712f_wdt.o
 obj-$(CONFIG_IT87_WDT) += it87_wdt.o
+obj-$(CONFIG_JNX_PTXPMB_WDT) += ptxpmb_wdt.o
 obj-$(CONFIG_HP_WATCHDOG) += hpwdt.o
 obj-$(CONFIG_KEMPLD_WDT) += kempld_wdt.o
 obj-$(CONFIG_SC1200_WDT) += sc1200wdt.o
diff --git a/drivers/watchdog/ptxpmb_wdt.c b/drivers/watchdog/ptxpmb_wdt.c
new file mode 100644
index 0000000..f04ac50
--- /dev/null
+++ b/drivers/watchdog/ptxpmb_wdt.c
@@ -0,0 +1,283 @@ 
+/*
+ * Watchdog driver for PTX PMB CPLD based watchdog
+ *
+ * Copyright (C) 2012 Juniper Networks
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ */
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/watchdog.h>
+#include <linux/reboot.h>
+#include <linux/notifier.h>
+#include <linux/ioport.h>
+#include <linux/mm.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+#include <linux/uaccess.h>
+#include <linux/watchdog.h>
+#include <linux/spinlock.h>
+#include <linux/mfd/ptxpmb_cpld.h>
+
+#define DRV_NAME "jnx-ptxpmb-wdt"
+
+/*
+ * Since we can't really expect userspace to be responsive enough before a
+ * watchdog overflow happens, we maintain two separate timers .. One in
+ * the kernel for clearing out the watchdog every second, and another for
+ * monitoring userspace writes to the WDT device.
+ *
+ * As such, we currently use a configurable heartbeat interval which defaults
+ * to 30s. In this case, the userspace daemon is only responsible for periodic
+ * writes to the device before the next heartbeat is scheduled. If the daemon
+ * misses its deadline, the kernel timer will allow the WDT to overflow.
+ */
+
+#define WD_MIN_TIMEOUT		1
+#define WD_MAX_TIMEOUT		65535
+#define WD_DEFAULT_TIMEOUT	30	/* 30 sec default heartbeat */
+
+struct ptxpmb_wdt {
+	struct pmb_boot_cpld __iomem *cpld;
+	struct device		*dev;
+	spinlock_t		lock;
+	struct timer_list	timer;
+	unsigned long		next_heartbeat;
+};
+
+static void ptxpmb_wdt_enable(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+
+	wdt->next_heartbeat = jiffies + wdog->timeout * HZ;
+	mod_timer(&wdt->timer, jiffies + HZ);
+
+	iowrite8(0, &wdt->cpld->watchdog_hbyte);
+	iowrite8(0, &wdt->cpld->watchdog_lbyte);
+	iowrite8(ioread8(&wdt->cpld->control) | 0x40, &wdt->cpld->control);
+}
+
+static void ptxpmb_wdt_disable(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+
+	del_timer(&wdt->timer);
+
+	iowrite8(ioread8(&wdt->cpld->control) & ~0x40, &wdt->cpld->control);
+}
+
+static int ptxpmb_wdt_keepalive(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	wdt->next_heartbeat = jiffies + (wdog->timeout * HZ);
+	spin_unlock_irqrestore(&wdt->lock, flags);
+
+	return 0;
+}
+
+static int ptxpmb_wdt_set_timeout(struct watchdog_device *wdog, unsigned int t)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	wdog->timeout = t;
+	ptxpmb_wdt_enable(wdog);
+	spin_unlock_irqrestore(&wdt->lock, flags);
+
+	return 0;
+}
+
+static void ptxpmb_wdt_ping(unsigned long data)
+{
+	struct ptxpmb_wdt *wdt = (struct ptxpmb_wdt *)data;
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	if (time_before(jiffies, wdt->next_heartbeat)) {
+		mod_timer(&wdt->timer, jiffies + HZ);
+		iowrite8(0, &wdt->cpld->watchdog_hbyte);
+		iowrite8(0, &wdt->cpld->watchdog_lbyte);
+	} else {
+		dev_warn(wdt->dev, "Heartbeat lost! Will not ping the watchdog\n");
+	}
+	spin_unlock_irqrestore(&wdt->lock, flags);
+}
+
+static int ptxpmb_wdt_start(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	ptxpmb_wdt_enable(wdog);
+	spin_unlock_irqrestore(&wdt->lock, flags);
+
+	return 0;
+}
+
+static int ptxpmb_wdt_stop(struct watchdog_device *wdog)
+{
+	struct ptxpmb_wdt *wdt = watchdog_get_drvdata(wdog);
+	unsigned long flags;
+
+	spin_lock_irqsave(&wdt->lock, flags);
+	ptxpmb_wdt_disable(wdog);
+	spin_unlock_irqrestore(&wdt->lock, flags);
+
+	return 0;
+}
+
+static const struct watchdog_info ptxpmb_wdt_info = {
+	.options		= WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT,
+	.identity		= "PTX PMB WDT",
+};
+
+static const struct watchdog_ops ptxpmb_wdt_ops = {
+	.owner = THIS_MODULE,
+	.start = ptxpmb_wdt_start,
+	.stop = ptxpmb_wdt_stop,
+	.ping = ptxpmb_wdt_keepalive,
+	.set_timeout = ptxpmb_wdt_set_timeout,
+};
+
+static struct watchdog_device *ptxpmb_wdog;
+
+static int ptxpmb_wdt_notify_sys(struct notifier_block *this,
+				 unsigned long code, void *unused)
+{
+	if (code == SYS_DOWN || code == SYS_HALT)
+		ptxpmb_wdt_stop(ptxpmb_wdog);
+
+	return NOTIFY_DONE;
+}
+
+static struct notifier_block ptxpmb_wdt_notifier = {
+	.notifier_call		= ptxpmb_wdt_notify_sys,
+};
+
+static int ptxpmb_wdt_probe(struct platform_device *pdev)
+{
+	struct ptxpmb_wdt *wdt;
+	bool nowayout = WATCHDOG_NOWAYOUT;
+	struct watchdog_device *wdog;
+	struct resource *res;
+	struct device *dev = &pdev->dev;
+	int rc;
+
+	wdog = devm_kzalloc(dev, sizeof(*wdog), GFP_KERNEL);
+	if (unlikely(!wdog))
+		return -ENOMEM;
+
+	res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (unlikely(!res))
+		return -EINVAL;
+
+#if 0
+	if (!devm_request_mem_region(dev, res->start,
+				     resource_size(res), DRV_NAME))
+		return -EBUSY;
+#endif
+
+	wdt = devm_kzalloc(dev, sizeof(*wdt), GFP_KERNEL);
+	if (unlikely(!wdt))
+		return -ENOMEM;
+
+	wdt->dev = dev;
+
+	wdt->cpld = devm_ioremap(dev, res->start, resource_size(res));
+	if (unlikely(!wdt->cpld))
+		return -ENXIO;
+
+	spin_lock_init(&wdt->lock);
+
+	wdog->info = &ptxpmb_wdt_info;
+	wdog->ops = &ptxpmb_wdt_ops;
+	wdog->min_timeout = WD_MIN_TIMEOUT;
+	wdog->max_timeout = WD_MAX_TIMEOUT;
+	wdog->timeout = WD_DEFAULT_TIMEOUT;
+	wdog->parent = dev;
+
+	watchdog_set_drvdata(wdog, wdt);
+	watchdog_set_nowayout(wdog, nowayout);
+	platform_set_drvdata(pdev, wdog);
+
+	ptxpmb_wdt_disable(wdog);
+	ptxpmb_wdog = wdog;
+
+	rc = register_reboot_notifier(&ptxpmb_wdt_notifier);
+	if (unlikely(rc)) {
+		dev_err(dev, "Can't register reboot notifier (err=%d)\n", rc);
+		return rc;
+	}
+
+	rc = watchdog_register_device(wdog);
+	if (rc)
+		goto out_unreg;
+
+	init_timer(&wdt->timer);
+	wdt->timer.function	= ptxpmb_wdt_ping;
+	wdt->timer.data		= (unsigned long)wdt;
+	wdt->timer.expires	= jiffies + HZ;
+
+	dev_info(dev, "initialized\n");
+
+	return 0;
+
+out_unreg:
+	unregister_reboot_notifier(&ptxpmb_wdt_notifier);
+	return rc;
+}
+
+static int ptxpmb_wdt_remove(struct platform_device *pdev)
+{
+	struct watchdog_device *wdog = platform_get_drvdata(pdev);
+
+	unregister_reboot_notifier(&ptxpmb_wdt_notifier);
+	watchdog_unregister_device(wdog);
+
+	return 0;
+}
+
+static const struct of_device_id ptxpmb_wdt_of_ids[] = {
+	{ .compatible = "jnx,ptxpmb-wdt", NULL },
+	{ }
+};
+MODULE_DEVICE_TABLE(of, ptxpmb_wdt_of_ids);
+
+static struct platform_driver ptxpmb_wdt_driver = {
+	.driver		= {
+		.name	= DRV_NAME,
+		.owner	= THIS_MODULE,
+		.of_match_table = ptxpmb_wdt_of_ids,
+	},
+
+	.probe	= ptxpmb_wdt_probe,
+	.remove	= ptxpmb_wdt_remove,
+};
+
+static int __init ptxpmb_wdt_init(void)
+{
+	return platform_driver_register(&ptxpmb_wdt_driver);
+}
+
+static void __exit ptxpmb_wdt_exit(void)
+{
+	platform_driver_unregister(&ptxpmb_wdt_driver);
+}
+module_init(ptxpmb_wdt_init);
+module_exit(ptxpmb_wdt_exit);
+
+MODULE_AUTHOR("Guenter Roeck <groeck@juniper.net>");
+MODULE_DESCRIPTION("Juniper PTX PMB CPLD watchdog driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:" DRV_NAME);