summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAhmad Fatoum <a.fatoum@pengutronix.de>2020-07-01 11:11:03 +0200
committerSascha Hauer <s.hauer@pengutronix.de>2020-07-11 06:14:04 +0200
commitc4c5f9813c1197d1f67fabe18e5c43d6dbe91ee3 (patch)
treeeecda1be6534f526e4f234fe58a5d35586318d62
parent0dbd1a8bd5399ee81a657e69da04b64d9d6756ef (diff)
downloadbarebox-c4c5f9813c1197d1f67fabe18e5c43d6dbe91ee3.tar.gz
barebox-c4c5f9813c1197d1f67fabe18e5c43d6dbe91ee3.tar.xz
watchdog: add support for at91sam9/sama5 watchdog
The watchdog on these SoCs is enabled by default on system boot, so a driver is especially useful. According to data sheet the mode register containing the timeout can be configured only once, but I couldn't verify this on the sama5d2. Regardless, the driver takes care not to change the mode register unless necessary. Implementation that want to leave to the OS the decision which timeout to choose, can just keep pinging with the POR-default of 16 seconds and the OS will be able to set the final timeout. Signed-off-by: Ahmad Fatoum <a.fatoum@pengutronix.de> Signed-off-by: Sascha Hauer <s.hauer@pengutronix.de>
-rw-r--r--drivers/watchdog/Kconfig6
-rw-r--r--drivers/watchdog/Makefile1
-rw-r--r--drivers/watchdog/at91sam9_wdt.c109
3 files changed, 116 insertions, 0 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index d9734ef588..cf83b6a15b 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -22,6 +22,12 @@ config WATCHDOG_AR9344
help
Add support for watchdog on the QCA AR9344 SoC.
+config WATCHDOG_AT91SAM9
+ bool "Watchdog for AT91SAM9 and SAMA5 SoCs"
+ depends on ARCH_AT91
+ help
+ Support for the watchdog in AT91SAM9X and SAMA5D{2,3,4} SoCs.
+
config WATCHDOG_EFI
bool "Generic EFI Watchdog Driver"
depends on EFI_BOOTUP
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 3af64db3f2..dc9842770a 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -1,5 +1,6 @@
obj-$(CONFIG_WATCHDOG) += wd_core.o
obj-$(CONFIG_WATCHDOG_AR9344) += ar9344_wdt.o
+obj-$(CONFIG_WATCHDOG_AT91SAM9) += at91sam9_wdt.o
obj-$(CONFIG_WATCHDOG_EFI) += efi_wdt.o
obj-$(CONFIG_WATCHDOG_DAVINCI) += davinci_wdt.o
obj-$(CONFIG_WATCHDOG_OMAP) += omap_wdt.o
diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c
new file mode 100644
index 0000000000..3f554bf47b
--- /dev/null
+++ b/drivers/watchdog/at91sam9_wdt.c
@@ -0,0 +1,109 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2019 Pengutronix, Ahmad Fatoum <a.fatoum@pengutronix.de>
+ */
+
+#include <common.h>
+#include <init.h>
+#include <io.h>
+#include <watchdog.h>
+#include <linux/clk.h>
+#include <mach/at91_wdt.h>
+
+#define MIN_WDT_TIMEOUT 1
+#define MAX_WDT_TIMEOUT 16
+#define SECS_TO_WDOG_TICKS(s) ((s) ? (((s) << 8) - 1) : 0)
+
+struct at91sam9x_wdt {
+ struct watchdog wdd;
+ void __iomem *base;
+};
+
+static inline void at91sam9x_wdt_ping(struct at91sam9x_wdt *wdt)
+{
+ writel(AT91_WDT_WDRSTT | AT91_WDT_KEY, wdt->base + AT91_WDT_CR);
+}
+
+static int at91sam9x_wdt_set_timeout(struct watchdog *wdd, unsigned timeout)
+{
+ struct at91sam9x_wdt *wdt = container_of(wdd, struct at91sam9x_wdt, wdd);
+ u32 mr_old, mr_new;
+
+ mr_old = readl(wdt->base + AT91_WDT_MR);
+
+ if (!timeout) {
+ mr_new = mr_old | AT91_WDT_WDDIS;
+ writel(mr_new, wdt->base + AT91_WDT_MR);
+ return 0;
+ }
+
+ mr_new = AT91_WDT_WDRSTEN
+ | AT91_WDT_WDDBGHLT | AT91_WDT_WDIDLEHLT
+ | AT91_WDT_WDD
+ | (SECS_TO_WDOG_TICKS(timeout) & AT91_WDT_WDV);
+
+ if (mr_new != mr_old)
+ writel(mr_new, wdt->base + AT91_WDT_MR);
+
+ at91sam9x_wdt_ping(wdt);
+ return 0;
+}
+
+static inline bool at91sam9x_wdt_is_disabled(struct at91sam9x_wdt *wdt)
+{
+ return readl(wdt->base + AT91_WDT_MR) & AT91_WDT_WDDIS;
+}
+
+static int at91sam9x_wdt_probe(struct device_d *dev)
+{
+ struct at91sam9x_wdt *wdt;
+ struct resource *iores;
+ struct clk *clk;
+ int ret;
+
+ wdt = xzalloc(sizeof(*wdt));
+ iores = dev_request_mem_resource(dev, 0);
+ if (IS_ERR(iores)) {
+ dev_err(dev, "could not get watchdog memory region\n");
+ return PTR_ERR(iores);
+ }
+ wdt->base = IOMEM(iores->start);
+ clk = clk_get(dev, NULL);
+ if (WARN_ON(IS_ERR(clk)))
+ return PTR_ERR(clk);
+
+ clk_enable(clk);
+
+ wdt->wdd.set_timeout = at91sam9x_wdt_set_timeout;
+ wdt->wdd.timeout_max = MAX_WDT_TIMEOUT;
+ wdt->wdd.hwdev = dev;
+
+ if (at91sam9x_wdt_is_disabled(wdt))
+ wdt->wdd.running = WDOG_HW_NOT_RUNNING;
+ else
+ wdt->wdd.running = WDOG_HW_RUNNING;
+
+ ret = watchdog_register(&wdt->wdd);
+ if (ret)
+ free(wdt);
+
+ return ret;
+}
+
+static const __maybe_unused struct of_device_id at91sam9x_wdt_dt_ids[] = {
+ { .compatible = "atmel,at91sam9260-wdt", },
+ { .compatible = "atmel,sama5d4-wdt", },
+ { /* sentinel */ },
+};
+
+static struct driver_d at91sam9x_wdt_driver = {
+ .name = "at91sam9x-wdt",
+ .of_compatible = DRV_OF_COMPAT(at91sam9x_wdt_dt_ids),
+ .probe = at91sam9x_wdt_probe,
+};
+
+static int __init at91sam9x_wdt_init(void)
+{
+ return platform_driver_register(&at91sam9x_wdt_driver);
+}
+device_initcall(at91sam9x_wdt_init);