|
|
|
Add a watchdog driver for ARM MPcore processors.
|
|
|
|
|
|
|
|
Signed-off-by: Felix Fietkau <nbd@nbd.name>
|
|
|
|
---
|
|
|
|
--- a/drivers/watchdog/Kconfig
|
|
|
|
+++ b/drivers/watchdog/Kconfig
|
|
|
|
@@ -355,6 +355,13 @@ config KS8695_WATCHDOG
|
|
|
|
Watchdog timer embedded into KS8695 processor. This will reboot your
|
|
|
|
system when the timeout is reached.
|
|
|
|
|
|
|
|
+config MPCORE_WATCHDOG
|
|
|
|
+ tristate "MPcore watchdog"
|
|
|
|
+ depends on HAVE_ARM_TWD
|
|
|
|
+ select WATCHDOG_CORE
|
|
|
|
+ help
|
|
|
|
+ Watchdog timer embedded into the MPcore system
|
|
|
|
+
|
|
|
|
config HAVE_S3C2410_WATCHDOG
|
|
|
|
bool
|
|
|
|
help
|
|
|
|
--- a/drivers/watchdog/Makefile
|
|
|
|
+++ b/drivers/watchdog/Makefile
|
|
|
|
@@ -49,6 +49,7 @@ obj-$(CONFIG_977_WATCHDOG) += wdt977.o
|
|
|
|
obj-$(CONFIG_GEMINI_WATCHDOG) += gemini_wdt.o
|
|
|
|
obj-$(CONFIG_IXP4XX_WATCHDOG) += ixp4xx_wdt.o
|
|
|
|
obj-$(CONFIG_KS8695_WATCHDOG) += ks8695_wdt.o
|
|
|
|
+obj-$(CONFIG_MPCORE_WATCHDOG) += mpcore_wdt.o
|
|
|
|
obj-$(CONFIG_S3C2410_WATCHDOG) += s3c2410_wdt.o
|
|
|
|
obj-$(CONFIG_SA1100_WATCHDOG) += sa1100_wdt.o
|
|
|
|
obj-$(CONFIG_SAMA5D4_WATCHDOG) += sama5d4_wdt.o
|
|
|
|
--- /dev/null
|
|
|
|
+++ b/drivers/watchdog/mpcore_wdt.c
|
|
|
|
@@ -0,0 +1,118 @@
|
|
|
|
+/*
|
|
|
|
+ * Watchdog driver for ARM MPcore
|
|
|
|
+ *
|
|
|
|
+ * Copyright (C) 2017 Felix Fietkau <nbd@nbd.name>
|
|
|
|
+ */
|
|
|
|
+
|
|
|
|
+#include <linux/export.h>
|
|
|
|
+#include <linux/module.h>
|
|
|
|
+#include <linux/kernel.h>
|
|
|
|
+#include <linux/watchdog.h>
|
|
|
|
+#include <linux/platform_device.h>
|
|
|
|
+#include <linux/io.h>
|
|
|
|
+#include <asm/smp_twd.h>
|
|
|
|
+
|
|
|
|
+static void __iomem *wdt_base;
|
|
|
|
+static int wdt_timeout = 60;
|
|
|
|
+
|
|
|
|
+static int mpcore_wdt_keepalive(struct watchdog_device *wdd)
|
|
|
|
+{
|
|
|
|
+ static int perturb;
|
|
|
|
+ u32 count;
|
|
|
|
+
|
|
|
|
+ count = (twd_timer_get_rate() / 256) * wdt_timeout;
|
|
|
|
+
|
|
|
|
+ /* Reload register needs a different value on each refresh */
|
|
|
|
+ count += perturb;
|
|
|
|
+ perturb = !perturb;
|
|
|
|
+
|
|
|
|
+ iowrite32(count, wdt_base + TWD_WDOG_LOAD);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int mpcore_wdt_start(struct watchdog_device *wdd)
|
|
|
|
+{
|
|
|
|
+ mpcore_wdt_keepalive(wdd);
|
|
|
|
+
|
|
|
|
+ /* prescale = 256, mode = 1, enable = 1 */
|
|
|
|
+ iowrite32(0x0000FF09, wdt_base + TWD_WDOG_CONTROL);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int mpcore_wdt_stop(struct watchdog_device *wdd)
|
|
|
|
+{
|
|
|
|
+ iowrite32(0x12345678, wdt_base + TWD_WDOG_DISABLE);
|
|
|
|
+ iowrite32(0x87654321, wdt_base + TWD_WDOG_DISABLE);
|
|
|
|
+ iowrite32(0x0, wdt_base + TWD_WDOG_CONTROL);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int mpcore_wdt_set_timeout(struct watchdog_device *wdd,
|
|
|
|
+ unsigned int timeout)
|
|
|
|
+{
|
|
|
|
+ mpcore_wdt_stop(wdd);
|
|
|
|
+ wdt_timeout = timeout;
|
|
|
|
+ mpcore_wdt_start(wdd);
|
|
|
|
+
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static const struct watchdog_info mpcore_wdt_info = {
|
|
|
|
+ .options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
|
|
|
|
+ .identity = "MPcore Watchdog",
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static const struct watchdog_ops mpcore_wdt_ops = {
|
|
|
|
+ .owner = THIS_MODULE,
|
|
|
|
+ .start = mpcore_wdt_start,
|
|
|
|
+ .stop = mpcore_wdt_stop,
|
|
|
|
+ .ping = mpcore_wdt_keepalive,
|
|
|
|
+ .set_timeout = mpcore_wdt_set_timeout,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static struct watchdog_device mpcore_wdt = {
|
|
|
|
+ .info = &mpcore_wdt_info,
|
|
|
|
+ .ops = &mpcore_wdt_ops,
|
|
|
|
+ .min_timeout = 1,
|
|
|
|
+ .max_timeout = 65535,
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+static int mpcore_wdt_probe(struct platform_device *pdev)
|
|
|
|
+{
|
|
|
|
+ struct resource *res;
|
|
|
|
+ unsigned long rate = twd_timer_get_rate();
|
|
|
|
+
|
|
|
|
+ pr_info("MPCore WD init. clockrate: %u prescaler: %u countrate: %u timeout: %us\n", rate, 256, rate / 256, wdt_timeout);
|
|
|
|
+
|
|
|
|
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
|
|
|
|
+ if (!res)
|
|
|
|
+ return -ENODEV;
|
|
|
|
+
|
|
|
|
+ wdt_base = devm_ioremap_resource(&pdev->dev, res);
|
|
|
|
+ if (IS_ERR(wdt_base))
|
|
|
|
+ return PTR_ERR(wdt_base);
|
|
|
|
+
|
|
|
|
+ watchdog_register_device(&mpcore_wdt);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static int mpcore_wdt_remove(struct platform_device *dev)
|
|
|
|
+{
|
|
|
|
+ watchdog_unregister_device(&mpcore_wdt);
|
|
|
|
+ return 0;
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+static struct platform_driver mpcore_wdt_driver = {
|
|
|
|
+ .probe = mpcore_wdt_probe,
|
|
|
|
+ .remove = mpcore_wdt_remove,
|
|
|
|
+ .driver = {
|
|
|
|
+ .name = "mpcore_wdt",
|
|
|
|
+ },
|
|
|
|
+};
|
|
|
|
+
|
|
|
|
+module_platform_driver(mpcore_wdt_driver);
|
|
|
|
+MODULE_AUTHOR("Felix Fietkau <nbd@nbd.name>");
|
|
|
|
+MODULE_LICENSE("GPL");
|
|
|
|
--- a/arch/arm/include/asm/smp_twd.h
|
|
|
|
+++ b/arch/arm/include/asm/smp_twd.h
|
|
|
|
@@ -34,5 +34,6 @@ struct twd_local_timer name __initdata =
|
|
|
|
};
|
|
|
|
|
|
|
|
int twd_local_timer_register(struct twd_local_timer *);
|
|
|
|
+unsigned long twd_timer_get_rate(void);
|
|
|
|
|
|
|
|
#endif
|
|
|
|
--- a/arch/arm/kernel/smp_twd.c
|
|
|
|
+++ b/arch/arm/kernel/smp_twd.c
|
|
|
|
@@ -15,6 +15,7 @@
|
|
|
|
#include <linux/delay.h>
|
|
|
|
#include <linux/device.h>
|
|
|
|
#include <linux/err.h>
|
|
|
|
+#include <linux/export.h>
|
|
|
|
#include <linux/smp.h>
|
|
|
|
#include <linux/jiffies.h>
|
|
|
|
#include <linux/clockchips.h>
|
|
|
|
@@ -380,6 +381,14 @@ int __init twd_local_timer_register(stru
|
|
|
|
return twd_local_timer_common_register(NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
+/* Needed by mpcore_wdt */
|
|
|
|
+unsigned long twd_timer_get_rate(void)
|
|
|
|
+{
|
|
|
|
+ return twd_timer_rate;
|
|
|
|
+}
|
|
|
|
+EXPORT_SYMBOL_GPL(twd_timer_get_rate);
|
|
|
|
+
|
|
|
|
+
|
|
|
|
#ifdef CONFIG_OF
|
|
|
|
static int __init twd_local_timer_of_register(struct device_node *np)
|
|
|
|
{
|