From: Guenter Roeck <groeck@xxxxxxxxxxx> Add support for Junipers PTXPMB CPLD's watchdog device. Signed-off-by: Guenter Roeck <groeck@xxxxxxxxxxx> [Ported from Juniper kernel] Signed-off-by: Pantelis Antoniou <pantelis.antoniou@xxxxxxxxxxxx> --- 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 --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@xxxxxxxxxxx>"); +MODULE_DESCRIPTION("Juniper PTX PMB CPLD watchdog driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); -- 1.9.1 -- To unsubscribe from this list: send the line "unsubscribe linux-gpio" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html