watchdog: qcom: introduce qcom-wdt driver

Some Qualcomm device vendors decide to turn the watchdog on in the
bootloader, resulting in the device being reset if it isn't petted every
~30 seconds. Introduce a driver to keep the watchdog happy and prevent this
annoying behaviour.

Signed-off-by: Paul Sajna <hello@paulsajna.com>
Co-authored-by: Paul Sajna <hello@paulsajna.com>
Tested-by: Paul Sajna <hello@paulsajna.com>
Reviewed-by: Stefan Roese <sr@denx.de>
Acked-by: Sumit Garg <sumit.garg@oss.qualcomm.com>
Reviewed-by: Neil Armstrong <neil.armstrong@linaro.org>
Link: https://lore.kernel.org/r/20250422-b4-qcom-wdt-v3-1-730d4d5a858d@paulsajna.com
Signed-off-by: Casey Connolly <casey.connolly@linaro.org>
This commit is contained in:
Casey Connolly 2025-04-23 02:19:39 +00:00
parent cb99e1257b
commit 530764de9f
4 changed files with 127 additions and 0 deletions

View File

@ -143,3 +143,5 @@ CONFIG_VIDEO_FONT_16X32=y
CONFIG_SYS_WHITE_ON_BLACK=y
CONFIG_NO_FB_CLEAR=y
CONFIG_VIDEO_SIMPLE=y
CONFIG_WDT_QCOM=y
CONFIG_WDT=y

View File

@ -335,6 +335,13 @@ config WDT_K3_RTI_FW_FILE
endif
config WDT_QCOM
bool "Qualcomm watchdog timer support"
depends on WDT && ARCH_SNAPDRAGON
help
Select this to enable Qualcomm watchdog timer, which can be found on
some Qualcomm chips.
config WDT_RENESAS
bool "Renesas watchdog timer support"
depends on WDT && R8A779F0

View File

@ -55,3 +55,4 @@ obj-$(CONFIG_WDT_SUNXI) += sunxi_wdt.o
obj-$(CONFIG_WDT_TANGIER) += tangier_wdt.o
obj-$(CONFIG_WDT_XILINX) += xilinx_wwdt.o
obj-$(CONFIG_WDT_ADI) += adi_wdt.o
obj-$(CONFIG_WDT_QCOM) += qcom-wdt.o

117
drivers/watchdog/qcom-wdt.c Normal file
View File

@ -0,0 +1,117 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (c) 2014, The Linux Foundation. All rights reserved.
* Copyright (c) Linaro Ltd. 2024
*
* Authors:
* Casey Connolly <casey.connolly@linaro.org>
* Paul Sajna <hello@paulsajna.com>
*
* Derived from linux/drivers/watchdog/qcom-wdt.c
*/
#include <dm.h>
#include <dm/device_compat.h>
#include <wdt.h>
#include <asm/io.h>
enum wdt_reg {
WDT_RST,
WDT_EN,
WDT_STS,
WDT_BARK_TIME,
WDT_BITE_TIME,
};
struct qcom_wdt_match_data {
const u32 *offset;
};
struct qcom_wdt {
void __iomem *base;
const u32 *layout;
};
static const u32 reg_offset_data_kpss[] = {
[WDT_RST] = 0x4,
[WDT_EN] = 0x8,
[WDT_STS] = 0xC,
[WDT_BARK_TIME] = 0x10,
[WDT_BITE_TIME] = 0x14,
};
static const struct qcom_wdt_match_data match_data_kpss = {
.offset = reg_offset_data_kpss,
};
static void __iomem *wdt_addr(struct qcom_wdt *wdt, enum wdt_reg reg)
{
return wdt->base + wdt->layout[reg];
}
int qcom_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags)
{
struct qcom_wdt *wdt = dev_get_priv(dev);
writel(0, wdt_addr(wdt, WDT_EN));
writel(1, wdt_addr(wdt, WDT_RST));
writel(1, wdt_addr(wdt, WDT_EN));
if (readl(wdt_addr(wdt, WDT_EN)) != 1) {
dev_err(dev, "Failed to enable Qualcomm watchdog!\n");
return -EIO;
}
return 0;
}
int qcom_wdt_stop(struct udevice *dev)
{
struct qcom_wdt *wdt = dev_get_priv(dev);
writel(0, wdt_addr(wdt, WDT_EN));
if (readl(wdt_addr(wdt, WDT_EN))) {
dev_err(dev, "Failed to disable Qualcomm watchdog!\n");
return -EIO;
}
return 0;
}
int qcom_wdt_reset(struct udevice *dev)
{
struct qcom_wdt *wdt = dev_get_priv(dev);
writel(1, wdt_addr(wdt, WDT_RST));
return 0;
}
static int qcom_wdt_probe(struct udevice *dev)
{
struct qcom_wdt *wdt = dev_get_priv(dev);
struct qcom_wdt_match_data *data = (void *)dev_get_driver_data(dev);
wdt->base = dev_read_addr_ptr(dev);
wdt->layout = data->offset;
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 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,
.ops = &qcom_wdt_ops,
.probe = qcom_wdt_probe,
.priv_auto = sizeof(struct qcom_wdt),
};