watchdog: Add support for K3 RTI watchdog
authorJan Kiszka <jan.kiszka@siemens.com>
Tue, 23 Jun 2020 11:15:08 +0000 (13:15 +0200)
committerLokesh Vutla <lokeshvutla@ti.com>
Tue, 11 Aug 2020 04:48:27 +0000 (10:18 +0530)
This is based on the Linux kernel driver for the RTI watchdog.

To actually reset the system on an AM65x, it requires firmware running
on the R5 that accepts the NMI and issues the actual system reset via
TISCI. Kind of an iTCO, except that this watchdog hardware has support
for no-way-out, and only for that.

On the J721E, reset works without extra firmware help when routing the
RTI interrupt via the ESM.

Signed-off-by: Jan Kiszka <jan.kiszka@siemens.com>
drivers/watchdog/Kconfig
drivers/watchdog/Makefile
drivers/watchdog/rti_wdt.c [new file with mode: 0644]

index 6d5c4fcfebb0f472e78aa542c8e2068c37944aa6..0ebf116b129053647909ee96e58476498dc8d9b7 100644 (file)
@@ -155,6 +155,13 @@ config WDT_ORION
          Select this to enable Orion watchdog timer, which can be found on some
          Marvell Armada chips.
 
+config WDT_K3_RTI
+       bool "Texas Instruments K3 RTI watchdog"
+       depends on WDT && ARCH_K3
+       help
+         Say Y here if you want to include support for the K3 watchdog
+         timer (RTI module) available in the K3 generation of processors.
+
 config WDT_SANDBOX
        bool "Enable Watchdog Timer support for Sandbox"
        depends on SANDBOX && WDT
index 0f0b2eb5bcea1fd45f5b7e326b8d034e92b71013..111e25819527cb211cada9202a4eea3156ad638d 100644 (file)
@@ -28,6 +28,7 @@ obj-$(CONFIG_WDT_MT7621) += mt7621_wdt.o
 obj-$(CONFIG_WDT_MTK) += mtk_wdt.o
 obj-$(CONFIG_WDT_OMAP3) += omap_wdt.o
 obj-$(CONFIG_WDT_SBSA) += sbsa_gwdt.o
+obj-$(CONFIG_WDT_K3_RTI) += rti_wdt.o
 obj-$(CONFIG_WDT_SP805) += sp805_wdt.o
 obj-$(CONFIG_WDT_STM32MP) += stm32mp_wdt.o
 obj-$(CONFIG_WDT_TANGIER) += tangier_wdt.o
diff --git a/drivers/watchdog/rti_wdt.c b/drivers/watchdog/rti_wdt.c
new file mode 100644 (file)
index 0000000..ebe29c7
--- /dev/null
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) Siemens AG, 2020
+ *
+ * Authors:
+ *   Jan Kiszka <jan.kiszka@siemens.com>
+ *
+ * Derived from linux/drivers/watchdog/rti_wdt.c
+ */
+
+#include <common.h>
+#include <clk.h>
+#include <dm.h>
+#include <power-domain.h>
+#include <wdt.h>
+#include <asm/io.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_50P         0x50
+
+#define WDENABLE_KEY           0xa98559da
+
+#define WDKEY_SEQ0             0xe51a
+#define WDKEY_SEQ1             0xa35c
+
+#define WDT_PRELOAD_SHIFT      13
+
+#define WDT_PRELOAD_MAX                0xfff
+
+struct rti_wdt_priv {
+       phys_addr_t regs;
+       unsigned int clk_khz;
+};
+
+static int rti_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags)
+{
+       struct rti_wdt_priv *priv = dev_get_priv(dev);
+       u32 timer_margin;
+       int ret;
+
+       if (readl(priv->regs + RTIDWDCTRL) == WDENABLE_KEY)
+               return -EBUSY;
+
+       timer_margin = timeout_ms * priv->clk_khz / 1000;
+       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 int rti_wdt_reset(struct udevice *dev)
+{
+       struct rti_wdt_priv *priv = dev_get_priv(dev);
+       u32 prld;
+
+       /* Make sure we do not reset too early */
+       prld = readl(priv->regs + RTIDWDPRLD) << WDT_PRELOAD_SHIFT;
+       if (readl(priv->regs + RTIDWDCNTR) >= prld / 2)
+               return -EPERM;
+
+       writel(WDKEY_SEQ0, priv->regs + RTIWDKEY);
+       writel(WDKEY_SEQ1, priv->regs + RTIWDKEY);
+
+       return 0;
+}
+
+static int rti_wdt_probe(struct udevice *dev)
+{
+       struct rti_wdt_priv *priv = dev_get_priv(dev);
+       struct clk clk;
+       int ret;
+
+       priv->regs = devfdt_get_addr(dev);
+       if (!priv->regs)
+               return -EINVAL;
+
+       ret = clk_get_by_index(dev, 0, &clk);
+       if (ret)
+               return ret;
+
+       priv->clk_khz = clk_get_rate(&clk);
+
+       return 0;
+}
+
+static const struct wdt_ops rti_wdt_ops = {
+       .start = rti_wdt_start,
+       .reset = rti_wdt_reset,
+};
+
+static const struct udevice_id rti_wdt_ids[] = {
+       { .compatible = "ti,j7-rti-wdt" },
+       { }
+};
+
+U_BOOT_DRIVER(rti_wdt) = {
+       .name = "rti_wdt",
+       .id = UCLASS_WDT,
+       .of_match = rti_wdt_ids,
+       .ops = &rti_wdt_ops,
+       .probe = rti_wdt_probe,
+       .priv_auto_alloc_size = sizeof(struct rti_wdt_priv),
+       .flags = DM_FLAG_REMOVE_WITH_PD_ON,
+};