Signed-off-by: Sascha Hauer <[email protected]>
---
 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 <[email protected]>
+ *
+ * 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


Reply via email to