Signed-off-by: Sascha Hauer <s.hauer@xxxxxxxxxxxxxx> --- 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@xxxxxxxxxxx> + * + * 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