From patchwork Tue Jun 23 06:20:24 2020 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jan Kiszka X-Patchwork-Id: 242790 List-Id: U-Boot discussion From: jan.kiszka at siemens.com (Jan Kiszka) Date: Tue, 23 Jun 2020 08:20:24 +0200 Subject: [PATCH 1/3] watchdog: Add support for K3 RTI watchdog In-Reply-To: References: Message-ID: From: Jan Kiszka 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 --- drivers/watchdog/Kconfig | 7 ++ drivers/watchdog/Makefile | 1 + drivers/watchdog/rti_wdt.c | 133 +++++++++++++++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 drivers/watchdog/rti_wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index bf06180cdd..ee99bd2af1 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -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 diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index 519bbd3a40..16bdbf4970 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -27,6 +27,7 @@ obj-$(CONFIG_WDT_MPC8xx) += mpc8xx_wdt.o obj-$(CONFIG_WDT_MT7621) += mt7621_wdt.o obj-$(CONFIG_WDT_MTK) += mtk_wdt.o obj-$(CONFIG_WDT_OMAP3) += omap_wdt.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 index 0000000000..3089a22da6 --- /dev/null +++ b/drivers/watchdog/rti_wdt.c @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (c) Siemens AG, 2020 + * + * Authors: + * Jan Kiszka + * + * Derived from linux/drivers/watchdog/rti_wdt.c + */ + +#include +#include +#include +#include +#include +#include + +/* 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; + struct power_domain pwrdmn; +}; + +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; + + ret = power_domain_on(&priv->pwrdmn); + if (ret) { + dev_err(dev, "Power domain on failed\n"); + return 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); + + ret = power_domain_get_by_index(dev, &priv->pwrdmn, 0); + if (ret) + return ret; + + 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), +};