[PATCH 3/3] watchdog: add rti_wdt support
Sascha Hauer
s.hauer at pengutronix.de
Tue Nov 12 01:16:06 PST 2024
Signed-off-by: Sascha Hauer <s.hauer at pengutronix.de>
---
drivers/watchdog/Kconfig | 7 ++
drivers/watchdog/Makefile | 1 +
drivers/watchdog/rti_wdt.c | 183 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 191 insertions(+)
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig
index 762e37c9c2..62b44df7c1 100644
--- a/drivers/watchdog/Kconfig
+++ b/drivers/watchdog/Kconfig
@@ -177,4 +177,11 @@ config CADENCE_WATCHDOG
Say Y here if you want to include support for the watchdog
timer in the Xilinx Zynq.
+config K3_RTI_WDT
+ bool "Texas Instruments K3 RTI watchdog"
+ depends on ARCH_K3 || COMPILE_TEST
+ help
+ Say Y here if you want to include support for the K3 watchdog
+ timer (RTI module) available in the K3 generation of processors.
+
endif
diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile
index 2b0da7cea9..85d8dbfa3f 100644
--- a/drivers/watchdog/Makefile
+++ b/drivers/watchdog/Makefile
@@ -24,3 +24,4 @@ obj-$(CONFIG_ITCO_WDT) += itco_wdt.o
obj-$(CONFIG_STARFIVE_WDT) += starfive_wdt.o
obj-$(CONFIG_WDAT_WDT) += wdat_wdt.o
obj-$(CONFIG_CADENCE_WATCHDOG) += cadence_wdt.o
+obj-$(CONFIG_K3_RTI_WDT) += rti_wdt.o
diff --git a/drivers/watchdog/rti_wdt.c b/drivers/watchdog/rti_wdt.c
new file mode 100644
index 0000000000..9764bc5462
--- /dev/null
+++ b/drivers/watchdog/rti_wdt.c
@@ -0,0 +1,183 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) Siemens AG, 2020
+ *
+ * Authors:
+ * Jan Kiszka <jan.kiszka at siemens.com>
+ *
+ * Derived from linux/drivers/watchdog/rti_wdt.c
+ */
+#include <init.h>
+#include <io.h>
+#include <of.h>
+#include <clock.h>
+#include <malloc.h>
+#include <watchdog.h>
+#include <driver.h>
+#include <linux/clk.h>
+#include <linux/math64.h>
+
+/* Timer register set definition */
+#define RTIDWDCTRL 0x90
+#define RTIDWDPRLD 0x94
+#define RTIWDSTATUS 0x98
+#define RTIWDKEY 0x9c
+#define RTIDWDCNTR 0xa0
+#define RTIWWDRXCTRL 0xa4
+#define RTIWWDSIZECTRL 0xa8
+
+#define RTIWWDRX_NMI 0xa
+
+#define RTIWWDSIZE_100P 0x5
+#define RTIWWDSIZE_50P 0x50
+
+#define WDENABLE_KEY 0xa98559da
+
+#define WDKEY_SEQ0 0xe51a
+#define WDKEY_SEQ1 0xa35c
+
+#define WDT_PRELOAD_SHIFT 13
+
+#define WDT_PRELOAD_MAX 0xfff
+
+#define DWDST BIT(1)
+
+struct rti_wdt_priv {
+ void __iomem *regs;
+ struct watchdog wdt;
+ unsigned int clk_hz;
+};
+
+static int rti_wdt_ping(struct watchdog *wdt)
+{
+ struct rti_wdt_priv *priv = container_of(wdt, struct rti_wdt_priv, wdt);
+ u64 halftime;
+
+ halftime = wdt->timeout_cur / 2 + 1;
+
+ if (!is_timeout(wdt->last_ping, halftime * SECOND))
+ return -EBUSY;
+
+ writel(WDKEY_SEQ0, priv->regs + RTIWDKEY);
+ writel(WDKEY_SEQ1, priv->regs + RTIWDKEY);
+
+ return 0;
+}
+
+static int rti_wdt_settimeout(struct watchdog *wdt, unsigned int timeout)
+{
+ struct rti_wdt_priv *priv = container_of(wdt, struct rti_wdt_priv, wdt);
+ u32 timer_margin;
+
+ if (!timeout)
+ return -ENOSYS;
+
+ if (wdt->running == WDOG_HW_RUNNING && timeout == wdt->timeout_cur)
+ return rti_wdt_ping(wdt);
+
+ if (readl(priv->regs + RTIDWDCTRL) == WDENABLE_KEY)
+ return -ENOSYS;
+
+ timer_margin = timeout * priv->clk_hz;
+ timer_margin >>= WDT_PRELOAD_SHIFT;
+ if (timer_margin > WDT_PRELOAD_MAX)
+ timer_margin = WDT_PRELOAD_MAX;
+
+ writel(timer_margin, priv->regs + RTIDWDPRLD);
+ writel(RTIWWDRX_NMI, priv->regs + RTIWWDRXCTRL);
+ writel(RTIWWDSIZE_50P, priv->regs + RTIWWDSIZECTRL);
+
+ readl(priv->regs + RTIWWDSIZECTRL);
+
+ writel(WDENABLE_KEY, priv->regs + RTIDWDCTRL);
+
+ return 0;
+}
+
+static unsigned int rti_wdt_get_timeleft_s(struct watchdog *wdt)
+{
+ struct rti_wdt_priv *priv = container_of(wdt, struct rti_wdt_priv, wdt);
+ u32 timer_counter;
+ u32 val;
+
+ /* if timeout has occurred then return 0 */
+ val = readl(priv->regs + RTIWDSTATUS);
+ if (val & DWDST)
+ return 0;
+
+ timer_counter = readl(priv->regs + RTIDWDCNTR);
+
+ return timer_counter / priv->clk_hz;
+}
+
+static int rti_wdt_probe(struct device *dev)
+{
+ struct rti_wdt_priv *priv;
+ struct clk *clk;
+ struct watchdog *wdt;
+ static bool one = false;
+
+ if (one)
+ return 0;
+ one = true;
+
+ priv = xzalloc(sizeof(*priv));
+
+ wdt = &priv->wdt;
+
+ priv->regs = dev_request_mem_region(dev, 0);
+ if (IS_ERR(priv->regs))
+ return -EINVAL;
+
+ clk = clk_get(dev, NULL);
+ if (IS_ERR(clk))
+ return dev_err_probe(dev, PTR_ERR(clk), "No clock");
+
+ priv->clk_hz = clk_get_rate(clk);
+
+ /*
+ * If watchdog is running at 32k clock, it is not accurate.
+ * Adjust frequency down in this case so that it does not expire
+ * earlier than expected.
+ */
+ if (priv->clk_hz < 32768)
+ priv->clk_hz = priv->clk_hz * 9 / 10;
+
+ wdt = &priv->wdt;
+ wdt->name = "rti_wdt";
+ wdt->hwdev = dev;
+ wdt->set_timeout = rti_wdt_settimeout;
+ wdt->ping = rti_wdt_ping;
+ wdt->timeout_max = WDT_PRELOAD_MAX / (priv->clk_hz >> WDT_PRELOAD_SHIFT);
+
+ if (readl(priv->regs + RTIDWDCTRL) == WDENABLE_KEY) {
+ u64 heartbeat_s;
+ u32 last_ping_s;
+
+ wdt->running = WDOG_HW_RUNNING;
+
+ heartbeat_s = readl(priv->regs + RTIDWDPRLD);
+ heartbeat_s <<= WDT_PRELOAD_SHIFT;
+ do_div(heartbeat_s, priv->clk_hz);
+ wdt->timeout_cur = heartbeat_s;
+ last_ping_s = heartbeat_s - rti_wdt_get_timeleft_s(wdt) + 1;
+
+ wdt->last_ping = get_time_ns() - last_ping_s * SECOND;
+ } else {
+ wdt->running = WDOG_HW_NOT_RUNNING;
+ }
+
+ return watchdog_register(wdt);
+}
+
+static const struct of_device_id rti_wdt_of_match[] = {
+ { .compatible = "ti,j7-rti-wdt", },
+ { /* sentinel */ }
+};
+
+static struct driver rti_wdt_driver = {
+ .name = "rti-wdt",
+ .probe = rti_wdt_probe,
+ .of_match_table = rti_wdt_of_match,
+};
+device_platform_driver(rti_wdt_driver);
--
2.39.5
More information about the barebox
mailing list