84acff2865
The original implementation loaded the count register with (wrong) semi- random values due to its implemenation nature. If the wrongly calulated value was below the kickrate, the WD was triggered and rebooted the system. Rework this, partly based on upstream patches, to dynamically fetch the current clockrate and calculate the proper offset for the WD countdown register. Before: [ 143.800000] count val: 27219720 [ 148.820000] count val: 50623201 [ 153.830000] count val: 96425250 [ 158.830000] count val: 89735401 [ 163.840000] count val: 4756110 After: [ 0.700000] MPCore WD init. clockrate: 299984500 prescaler: 256 countrate: 1171814 timeout: 60s [ 358.530000] count val: 35154751 [ 363.540000] count val: 35154750 [ 368.540000] count val: 35154751 [ 373.550000] count val: 35154750 Signed-off-by: Koen Vandeputte <koen.vandeputte@ncentric.com>
185 lines
4.6 KiB
Diff
185 lines
4.6 KiB
Diff
Add a watchdog driver for ARM MPcore processors.
|
|
|
|
Signed-off-by: Felix Fietkau <nbd@nbd.name>
|
|
---
|
|
--- a/drivers/watchdog/Kconfig
|
|
+++ b/drivers/watchdog/Kconfig
|
|
@@ -324,6 +324,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
|
|
@@ -47,6 +47,7 @@ obj-$(CONFIG_21285_WATCHDOG) += wdt285.o
|
|
obj-$(CONFIG_977_WATCHDOG) += wdt977.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
|
|
@@ -33,5 +33,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)
|
|
{
|