Added support for watchdog timer in Qualcomm chipsets, by adding a Qualcomm watchdog driver. This is a port of the upstream Linux changes to U-Boot: https://web.git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/drivers/watchdog/qcom-wdt.c?id=3d25d46a255a83f94d7d4d4216f38aafc8e116b0
Signed-off-by: Balaji Selvanathan <balaji.selvanat...@oss.qualcomm.com> --- drivers/watchdog/Kconfig | 17 ++++ drivers/watchdog/Makefile | 2 + drivers/watchdog/qcom-wdt.c | 194 ++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 drivers/watchdog/qcom-wdt.c diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1bb67f50352..e9c39dd37c7 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -33,6 +33,7 @@ config WATCHDOG_TIMEOUT_MSECS default 16000 if ARCH_SUNXI default 5376 if ULP_WATCHDOG default 15000 if ARCH_BCM283X + default 30000 if ARCH_SNAPDRAGON default 60000 help Watchdog timeout in msec @@ -61,6 +62,14 @@ config OMAP_WATCHDOG help Say Y here to enable the OMAP3+ watchdog driver. +config QCOM_WATCHDOG + bool "QCOM watchdog timer support" + depends on ARCH_SNAPDRAGON + select HW_WATCHDOG + select CYCLIC + help + Say Y here to enable the Qualcomm watchdog driver. + config ULP_WATCHDOG bool "i.MX7ULP watchdog" help @@ -315,6 +324,13 @@ config WDT_K3_RTI 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_QCOM + bool "Qualcomm watchdog timer driver support" + depends on WDT && ARCH_SNAPDRAGON + imply WATCHDOG + help + Enables support for Qualcomm watchdog timer in Qualcomm chipsets. + if WDT_K3_RTI config WDT_K3_RTI_LOAD_FW @@ -459,4 +475,5 @@ config WDT_FTWDT010 help Faraday Technology ftwdt010 watchdog is an architecture independent watchdog. It is usually used in SoC chip design. + endmenu diff --git a/drivers/watchdog/Makefile b/drivers/watchdog/Makefile index e6bd4c587af..93de2b31cef 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -12,6 +12,7 @@ endif obj-$(CONFIG_S5P) += s5p_wdt.o obj-$(CONFIG_XILINX_TB_WATCHDOG) += xilinx_tb_wdt.o obj-$(CONFIG_OMAP_WATCHDOG) += omap_wdt.o +obj-$(CONFIG_QCOM_WATCHDOG) += qcom-wdt.o obj-$(CONFIG_DESIGNWARE_WATCHDOG) += designware_wdt.o obj-$(CONFIG_ULP_WATCHDOG) += ulp_wdog.o obj-$(CONFIG_$(PHASE_)WDT) += wdt-uclass.o @@ -43,6 +44,7 @@ obj-$(CONFIG_WDT_MTK) += mtk_wdt.o obj-$(CONFIG_WDT_NPCM) += npcm_wdt.o obj-$(CONFIG_WDT_OCTEONTX) += octeontx_wdt.o obj-$(CONFIG_WDT_OMAP3) += omap_wdt.o +obj-$(CONFIG_WDT_QCOM) += qcom-wdt.o obj-$(CONFIG_WDT_SBSA) += sbsa_gwdt.o obj-$(CONFIG_WDT_K3_RTI) += rti_wdt.o obj-$(CONFIG_WDT_SIEMENS_PMIC) += siemens_pmic_wdt.o diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c new file mode 100644 index 00000000000..2cb34ed8fcf --- /dev/null +++ b/drivers/watchdog/qcom-wdt.c @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2025 Qualcomm Innovation Center, Inc. All rights reserved. + */ + +#include <asm/io.h> +#include <dm.h> +#include <clk.h> +#include <linux/delay.h> +#include <wdt.h> + +enum wdt_reg { + WDT_RST, + WDT_EN, + WDT_STS, + WDT_BARK_TIME, + WDT_BITE_TIME, +}; + +#define QCOM_WDT_ENABLE BIT(0) +#define QCOM_WDT_NODE_PATH "/soc/watchdog@17c10000" +#define QCOM_WDT_BASE_ADDR 0x17c10000 + +static const u32 reg_offset_data_kpss[] = { + [WDT_RST] = 0x4, + [WDT_EN] = 0x8, + [WDT_STS] = 0xC, + [WDT_BARK_TIME] = 0x10, + [WDT_BITE_TIME] = 0x14, +}; + +struct qcom_wdt_match_data { + const u32 *offset; + bool pretimeout; + u32 max_tick_count; +}; + +struct qcom_wdt_priv { + unsigned long clk_rate; + void __iomem *base; + const u32 *layout; + unsigned int min_timeout; + unsigned int max_timeout; +}; + +static void qcom_watchdog_stop(struct qcom_wdt_priv *wdt) +{ + writel(0, wdt->base + wdt->layout[WDT_EN]); +} + +static void qcom_watchdog_reset(struct qcom_wdt_priv *wdt) +{ + writel(1, wdt->base + wdt->layout[WDT_RST]); +} + +static void qcom_watchdog_init(struct qcom_wdt_priv *wdt, u32 timeout) +{ + u32 timeout_bark = timeout + 1000; + + writel(0, wdt->base + wdt->layout[WDT_EN]); + writel(1, wdt->base + wdt->layout[WDT_RST]); + writel(timeout_bark, wdt->base + wdt->layout[WDT_BARK_TIME]); + writel(timeout, wdt->base + wdt->layout[WDT_BITE_TIME]); + writel(QCOM_WDT_ENABLE, wdt->base + wdt->layout[WDT_EN]); +} + +void hw_watchdog_reset(void) +{ + struct qcom_wdt_priv wdt; + + wdt.base = (void *)QCOM_WDT_BASE_ADDR; + wdt.layout = reg_offset_data_kpss; + + qcom_watchdog_reset(&wdt); +} + +void hw_watchdog_init(void) +{ + struct qcom_wdt_priv wdt; + struct clk clk; + int ret; + u32 timeout = 0; + + wdt.base = (void *)QCOM_WDT_BASE_ADDR; + wdt.layout = reg_offset_data_kpss; + + ofnode node = ofnode_path(QCOM_WDT_NODE_PATH); + + if (!ofnode_valid(node)) { + printf("Watchdog DTS Node not found\n"); + return; + } + + ret = clk_get_by_index_nodev(node, 0, &clk); + if (ret) { + printf("Clk not found\n"); + return; + } + + wdt.clk_rate = clk_get_rate(&clk); + if (!wdt.clk_rate) { + printf("Clk rate not found\n"); + return; + } + + timeout = (CONFIG_WATCHDOG_TIMEOUT_MSECS * wdt.clk_rate) / 1000; + + qcom_watchdog_init(&wdt, timeout); +} + +static int qcom_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags) +{ + struct qcom_wdt_priv *priv = dev_get_priv(dev); + u32 timeout = 0; + + timeout = (timeout_ms * priv->clk_rate) / 1000; + + qcom_watchdog_init(priv, timeout); + + return 0; +} + +static int qcom_wdt_stop(struct udevice *dev) +{ + struct qcom_wdt_priv *priv = dev_get_priv(dev); + + qcom_watchdog_stop(priv); + + return 0; +} + +static int qcom_wdt_reset(struct udevice *dev) +{ + struct qcom_wdt_priv *priv = dev_get_priv(dev); + + qcom_watchdog_reset(priv); + + return 0; +} + +static int qcom_wdt_probe(struct udevice *dev) +{ + int ret; + struct qcom_wdt_priv *priv = dev_get_priv(dev); + const struct qcom_wdt_match_data *data = + (const struct qcom_wdt_match_data *)dev_get_driver_data(dev); + + priv->base = dev_read_addr_ptr(dev); + + if (!priv->base) + return -EINVAL; + + struct clk clk; + + ret = clk_get_by_index(dev, 0, &clk); + if (ret) + return ret; + + priv->clk_rate = clk_get_rate(&clk); + if (!priv->clk_rate) + return -EINVAL; + + priv->layout = data->offset; + priv->min_timeout = 1; + priv->max_timeout = data->max_tick_count / priv->clk_rate; + + return 0; +} + +static const struct wdt_ops qcom_wdt_ops = { + .start = qcom_wdt_start, + .stop = qcom_wdt_stop, + .reset = qcom_wdt_reset, +}; + +static const struct qcom_wdt_match_data match_data_kpss = { + .offset = reg_offset_data_kpss, + .pretimeout = true, + .max_tick_count = 0xFFFFFU, +}; + +static const struct udevice_id qcom_wdt_ids[] = { + { .compatible = "qcom,kpss-wdt", .data = (ulong)&match_data_kpss }, + {} +}; + +U_BOOT_DRIVER(qcom_wdt) = { + .name = "qcom_wdt", + .id = UCLASS_WDT, + .of_match = qcom_wdt_ids, + .priv_auto = sizeof(struct qcom_wdt_priv), + .probe = qcom_wdt_probe, + .ops = &qcom_wdt_ops, +}; -- 2.34.1