diff options
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/Kconfig | 20 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 2 | ||||
-rw-r--r-- | drivers/watchdog/arm_smc_wdt.c | 17 | ||||
-rw-r--r-- | drivers/watchdog/at91sam9_wdt.c | 55 | ||||
-rw-r--r-- | drivers/watchdog/qcom-wdt.c | 134 | ||||
-rw-r--r-- | drivers/watchdog/renesas_wdt.c | 189 | ||||
-rw-r--r-- | drivers/watchdog/stm32mp_wdt.c | 33 | ||||
-rw-r--r-- | drivers/watchdog/wdt-uclass.c | 9 |
8 files changed, 442 insertions, 17 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1bb67f50352..56290b32bd9 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -15,6 +15,7 @@ config WATCHDOG_AUTOSTART bool "Automatically start watchdog timer" depends on WDT default n if ARCH_SUNXI + default n if ARCH_STM32MP default y help Automatically start watchdog timer and start servicing it during @@ -27,8 +28,9 @@ config WATCHDOG_AUTOSTART config WATCHDOG_TIMEOUT_MSECS int "Watchdog timeout in msec" - default 128000 if ARCH_MX31 || ARCH_MX5 || ARCH_MX6 + default 128000 if ARCH_MX5 || ARCH_MX6 default 128000 if ARCH_MX7 || ARCH_VF610 + default 30000 if ARCH_SNAPDRAGON default 30000 if ARCH_SOCFPGA default 16000 if ARCH_SUNXI default 5376 if ULP_WATCHDOG @@ -335,6 +337,22 @@ config WDT_K3_RTI_FW_FILE endif +config WDT_QCOM + bool "Qualcomm watchdog timer support" + depends on WDT && ARCH_SNAPDRAGON + imply WATCHDOG + 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 + select CLK + select CLK_RENESAS + help + Enables Renesas SoC R8A779F0 watchdog timer support. + 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 e6bd4c587af..d52d17e1c90 100644 --- a/drivers/watchdog/Makefile +++ b/drivers/watchdog/Makefile @@ -43,6 +43,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_RENESAS) += renesas_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 @@ -54,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 diff --git a/drivers/watchdog/arm_smc_wdt.c b/drivers/watchdog/arm_smc_wdt.c index 0ea44445700..f6854aa9ac9 100644 --- a/drivers/watchdog/arm_smc_wdt.c +++ b/drivers/watchdog/arm_smc_wdt.c @@ -46,6 +46,8 @@ static int smcwd_call(struct udevice *dev, enum smcwd_call call, return -ENODEV; if (res->a0 == PSCI_RET_INVALID_PARAMS) return -EINVAL; + if (res->a0 == PSCI_RET_DISABLED) + return -ENODATA; if (res->a0 != PSCI_RET_SUCCESS) return -EIO; @@ -99,6 +101,21 @@ static int smcwd_probe(struct udevice *dev) priv->min_timeout = res.a1; priv->max_timeout = res.a2; + /* If already started, then force u-boot to use it */ + err = smcwd_call(dev, SMCWD_GET_TIMELEFT, 0, NULL); + switch (err) { + case 0: + dev_dbg(dev, "Already started\n"); + wdt_set_force_autostart(dev); + break; + case -ENODATA: + dev_dbg(dev, "Not already started\n"); + break; + default: + /* Optional SMCWD_GET_TIMELEFT not implemented */ + break; + } + return 0; } diff --git a/drivers/watchdog/at91sam9_wdt.c b/drivers/watchdog/at91sam9_wdt.c index c809a8936b8..72e13787448 100644 --- a/drivers/watchdog/at91sam9_wdt.c +++ b/drivers/watchdog/at91sam9_wdt.c @@ -38,7 +38,7 @@ DECLARE_GLOBAL_DATA_PTR; */ static int at91_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags) { - struct at91_wdt_priv *priv = dev_get_priv(dev); + struct at91_wdt_priv *wdt = dev_get_priv(dev); u64 timeout; u32 ticks; @@ -49,7 +49,7 @@ static int at91_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags) ticks = WDT_SEC2TICKS(timeout); /* Check if disabled */ - if (readl(priv->regs + AT91_WDT_MR) & AT91_WDT_MR_WDDIS) { + if (readl(wdt->regs + AT91_WDT_MR) & wdt->wddis) { printf("sorry, watchdog is disabled\n"); return -1; } @@ -60,31 +60,41 @@ static int at91_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags) * Since WDV is a 12-bit counter, the maximum period is * 4096 / 256 = 16 seconds. */ - priv->regval = AT91_WDT_MR_WDRSTEN /* causes watchdog reset */ - | AT91_WDT_MR_WDDBGHLT /* disabled in debug mode */ - | AT91_WDT_MR_WDD(0xfff) /* restart at any time */ - | AT91_WDT_MR_WDV(ticks); /* timer value */ - writel(priv->regval, priv->regs + AT91_WDT_MR); + + if (wdt->mode == AT91_WDT_MODE_SAM9260) { + wdt->mr = AT91_WDT_MR_WDRSTEN /* causes watchdog reset */ + | AT91_WDT_MR_WDDBGHLT /* disabled in debug mode */ + | AT91_WDT_MR_WDD(0xfff) /* restart at any time */ + | AT91_WDT_MR_WDV(ticks); /* timer value */ + writel(wdt->mr, wdt->regs + AT91_WDT_MR); + } else if (wdt->mode == AT91_WDT_MODE_SAM9X60) { + writel(AT91_SAM9X60_WLR_COUNTER(ticks), /* timer value */ + wdt->regs + AT91_SAM9X60_WLR); + + wdt->mr = AT91_SAM9X60_MR_PERIODRST /* causes watchdog reset */ + | AT91_SAM9X60_MR_WDDBGHLT; /* disabled in debug mode */ + writel(wdt->mr, wdt->regs + AT91_WDT_MR); + } return 0; } static int at91_wdt_stop(struct udevice *dev) { - struct at91_wdt_priv *priv = dev_get_priv(dev); + struct at91_wdt_priv *wdt = dev_get_priv(dev); /* Disable Watchdog Timer */ - priv->regval |= AT91_WDT_MR_WDDIS; - writel(priv->regval, priv->regs + AT91_WDT_MR); + wdt->mr |= wdt->wddis; + writel(wdt->mr, wdt->regs + AT91_WDT_MR); return 0; } static int at91_wdt_reset(struct udevice *dev) { - struct at91_wdt_priv *priv = dev_get_priv(dev); + struct at91_wdt_priv *wdt = dev_get_priv(dev); - writel(AT91_WDT_CR_WDRSTT | AT91_WDT_CR_KEY, priv->regs + AT91_WDT_CR); + writel(AT91_WDT_CR_WDRSTT | AT91_WDT_CR_KEY, wdt->regs + AT91_WDT_CR); return 0; } @@ -96,18 +106,31 @@ static const struct wdt_ops at91_wdt_ops = { }; static const struct udevice_id at91_wdt_ids[] = { - { .compatible = "atmel,at91sam9260-wdt" }, + { .compatible = "atmel,at91sam9260-wdt", + .data = AT91_WDT_MODE_SAM9260 }, + { .compatible = "atmel,sama5d4-wdt", + .data = AT91_WDT_MODE_SAM9260 }, + { .compatible = "microchip,sam9x60-wdt", + .data = AT91_WDT_MODE_SAM9X60 }, + { .compatible = "microchip,sama7g5-wdt", + .data = AT91_WDT_MODE_SAM9X60 }, {} }; static int at91_wdt_probe(struct udevice *dev) { - struct at91_wdt_priv *priv = dev_get_priv(dev); + struct at91_wdt_priv *wdt = dev_get_priv(dev); - priv->regs = dev_remap_addr(dev); - if (!priv->regs) + wdt->regs = dev_remap_addr(dev); + if (!wdt->regs) return -EINVAL; + wdt->mode = dev_get_driver_data(dev); + if (wdt->mode == AT91_WDT_MODE_SAM9260) + wdt->wddis = AT91_WDT_MR_WDDIS; + else if (wdt->mode == AT91_WDT_MODE_SAM9X60) + wdt->wddis = AT91_SAM9X60_MR_WDDIS; + debug("%s: Probing wdt%u\n", __func__, dev_seq(dev)); return 0; diff --git a/drivers/watchdog/qcom-wdt.c b/drivers/watchdog/qcom-wdt.c new file mode 100644 index 00000000000..4b972cff72c --- /dev/null +++ b/drivers/watchdog/qcom-wdt.c @@ -0,0 +1,134 @@ +// 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 <clk.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; + ulong clk_rate; + 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); + ulong bark_timeout_s = ((timeout_ms - 1) * wdt->clk_rate) / 1000; + ulong bite_timeout_s = (timeout_ms * wdt->clk_rate) / 1000; + + writel(0, wdt_addr(wdt, WDT_EN)); + writel(BIT(0), wdt_addr(wdt, WDT_RST)); + writel(bark_timeout_s, wdt_addr(wdt, WDT_BARK_TIME)); + writel(bite_timeout_s, wdt_addr(wdt, WDT_BITE_TIME)); + writel(BIT(0), wdt_addr(wdt, WDT_EN)); + + 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 clk clk; + long rate; + int ret; + + 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; + + ret = clk_get_by_index(dev, 0, &clk); + if (ret) + return ret; + + rate = clk_get_rate(&clk); + if (rate <= 0) + return rate < 0 ? (int)rate : -EINVAL; + + wdt->clk_rate = (ulong)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 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), +}; diff --git a/drivers/watchdog/renesas_wdt.c b/drivers/watchdog/renesas_wdt.c new file mode 100644 index 00000000000..046e11915b9 --- /dev/null +++ b/drivers/watchdog/renesas_wdt.c @@ -0,0 +1,189 @@ +// SPDX-License-Identifier: GPL-2.0+ +// Copyright 2025 Red Hat, Inc., Shmuel Leib Melamud <smelamud@redhat.com> + +#include <asm/io.h> +#include <clk.h> +#include <dm.h> +#include <dm/device_compat.h> +#include <linux/delay.h> +#include <linux/iopoll.h> +#include <wdt.h> + +#define usleep_range(a, b) udelay((a)) + +struct rwdt { + u32 cnt; + u32 csra; + u32 csrb; +}; + +#define RWTCSRA_WOVF BIT(4) +#define RWTCSRA_WRFLG BIT(5) +#define RWTCSRA_TME BIT(7) + +#define CSR_MASK 0xA5A5A500 +#define CNT_MASK 0x5A5A0000 + +#define MAX_CNT_VALUE 65536 +/* + * In probe, clk_rate is checked to be not more than 16 bit * biggest clock + * divider (12 bits). d is only a factor to fully utilize the WDT counter and + * will not exceed its 16 bits. Thus, no overflow, we stay below 32 bits. + */ +#define MUL_BY_CLKS_PER_SEC(p, d) \ + DIV_ROUND_UP((d) * (p)->clk_rate, clk_divs[(p)->cks]) + +/* d is 16 bit, clk_divs 12 bit -> no 32 bit overflow */ +#define DIV_BY_CLKS_PER_SEC(p, d) ((d) * clk_divs[(p)->cks] / (p)->clk_rate) + +static const unsigned int clk_divs[] = { 1, 4, 16, 32, 64, 128, 1024, 4096 }; + +struct rwdt_priv { + struct rwdt __iomem *wdt; + unsigned long clk_rate; + u8 cks; + struct clk clk; +}; + +static void rwdt_wait_cycles(struct rwdt_priv *priv, unsigned int cycles) +{ + unsigned int delay; + + delay = DIV_ROUND_UP(cycles * 1000000, priv->clk_rate); + + usleep_range(delay, 2 * delay); +} + +static int rwdt_start(struct udevice *dev, u64 timeout, ulong flags) +{ + struct rwdt_priv *priv = dev_get_priv(dev); + u64 max_timeout; + u8 val; + + max_timeout = DIV_BY_CLKS_PER_SEC(priv, MAX_CNT_VALUE); + timeout = min(max_timeout, timeout / 1000); + + /* Stop the timer before we modify any register */ + val = readb_relaxed(&priv->wdt->csra) & ~RWTCSRA_TME; + writel_relaxed(val | CSR_MASK, &priv->wdt->csra); + /* Delay 2 cycles before setting watchdog counter */ + rwdt_wait_cycles(priv, 2); + + while (readb_relaxed(&priv->wdt->csra) & RWTCSRA_WRFLG) + cpu_relax(); + + writel_relaxed((MAX_CNT_VALUE - MUL_BY_CLKS_PER_SEC(priv, timeout)) + | CNT_MASK, &priv->wdt->cnt); + + writel_relaxed(priv->cks | RWTCSRA_TME | CSR_MASK, &priv->wdt->csra); + + return 0; +} + +static int rwdt_stop(struct udevice *dev) +{ + struct rwdt_priv *priv = dev_get_priv(dev); + + writel_relaxed(priv->cks | CSR_MASK, &priv->wdt->csra); + + return 0; +} + +static int rwdt_reset(struct udevice *dev) +{ + struct rwdt_priv *priv = dev_get_priv(dev); + u8 val; + + /* Stop the timer before we modify any register */ + val = readb_relaxed(&priv->wdt->csra) & ~RWTCSRA_TME; + writel_relaxed(val | CSR_MASK, &priv->wdt->csra); + /* Delay 2 cycles before setting watchdog counter */ + rwdt_wait_cycles(priv, 2); + + writel_relaxed(0xffff | CNT_MASK, &priv->wdt->cnt); + /* smallest divider to reboot soon */ + writel_relaxed(0 | CSR_MASK, &priv->wdt->csra); + + readb_poll_timeout(&priv->wdt->csra, val, !(val & RWTCSRA_WRFLG), 100); + + writel_relaxed(RWTCSRA_TME | CSR_MASK, &priv->wdt->csra); + + /* wait 2 cycles, so watchdog will trigger */ + rwdt_wait_cycles(priv, 2); + + return 0; +} + +static int rwdt_probe(struct udevice *dev) +{ + struct rwdt_priv *priv = dev_get_priv(dev); + unsigned long clks_per_sec; + int ret, i; + + priv->wdt = dev_remap_addr(dev); + if (!priv->wdt) + return -EINVAL; + + ret = clk_get_by_index(dev, 0, &priv->clk); + if (ret < 0) + return ret; + + ret = clk_enable(&priv->clk); + if (ret) + return ret; + + priv->clk_rate = clk_get_rate(&priv->clk); + if (!priv->clk_rate) { + ret = -ENOENT; + goto err_clk_disable; + } + + /* + * Find the largest possible divider that allows clock rate + * (clks_per_sec) to stay within 16 bits. In this case, we can still + * measure the smallest timeout (1s) and make the largest allowed + * timeout as large as possible. + */ + for (i = ARRAY_SIZE(clk_divs) - 1; i >= 0; i--) { + clks_per_sec = priv->clk_rate / clk_divs[i]; + if (clks_per_sec && clks_per_sec < 65536) { + priv->cks = i; + break; + } + } + + /* can't find a suitable clock divider */ + if (i < 0) { + ret = -ERANGE; + goto err_clk_disable; + } + + return 0; + +err_clk_disable: + clk_disable(&priv->clk); + + return ret; +} + +static const struct wdt_ops rwdt_ops = { + .start = rwdt_start, + .reset = rwdt_reset, + .stop = rwdt_stop, +}; + +static const struct udevice_id rwdt_ids[] = { + { .compatible = "renesas,rcar-gen2-wdt" }, + { .compatible = "renesas,rcar-gen3-wdt" }, + { .compatible = "renesas,rcar-gen4-wdt" }, + {} +}; + +U_BOOT_DRIVER(wdt_renesas) = { + .name = "wdt_renesas", + .id = UCLASS_WDT, + .of_match = rwdt_ids, + .ops = &rwdt_ops, + .probe = rwdt_probe, + .priv_auto = sizeof(struct rwdt_priv), +}; diff --git a/drivers/watchdog/stm32mp_wdt.c b/drivers/watchdog/stm32mp_wdt.c index 97ab8cfe7ab..0712524b4a8 100644 --- a/drivers/watchdog/stm32mp_wdt.c +++ b/drivers/watchdog/stm32mp_wdt.c @@ -21,11 +21,13 @@ #define IWDG_PR 0x04 /* Prescaler Register */ #define IWDG_RLR 0x08 /* ReLoad Register */ #define IWDG_SR 0x0C /* Status Register */ +#define IWDG_VERR 0x3F4 /* Version Register */ /* IWDG_KR register bit mask */ #define KR_KEY_RELOAD 0xAAAA /* Reload counter enable */ #define KR_KEY_ENABLE 0xCCCC /* Peripheral enable */ #define KR_KEY_EWA 0x5555 /* Write access enable */ +#define KR_KEY_DWA 0x0000 /* Write access disable*/ /* IWDG_PR register bit values */ #define PR_256 0x06 /* Prescaler set to 256 */ @@ -36,10 +38,17 @@ /* IWDG_SR register bit values */ #define SR_PVU BIT(0) /* Watchdog prescaler value update */ #define SR_RVU BIT(1) /* Watchdog counter reload value update */ +#define SR_ONF BIT(8) /* Watchdog enable status bit */ + +/* IWDG Compatibility */ +#define ONF_MIN_VER 0x31 + +#define TIMEOUT_US 10000 struct stm32mp_wdt_priv { fdt_addr_t base; /* registers addr in physical memory */ unsigned long wdt_clk_rate; /* Watchdog dedicated clock rate */ + unsigned int hw_version; /* Peripheral version */ }; static int stm32mp_wdt_reset(struct udevice *dev) @@ -90,6 +99,7 @@ static int stm32mp_wdt_start(struct udevice *dev, u64 timeout_ms, ulong flags) static int stm32mp_wdt_probe(struct udevice *dev) { struct stm32mp_wdt_priv *priv = dev_get_priv(dev); + u32 rlr, sr; struct clk clk; int ret; @@ -115,6 +125,29 @@ static int stm32mp_wdt_probe(struct udevice *dev) priv->wdt_clk_rate = clk_get_rate(&clk); + priv->hw_version = readl(priv->base + IWDG_VERR); + + if (priv->hw_version >= ONF_MIN_VER) { + if (readl(priv->base + IWDG_SR) & SR_ONF) + wdt_set_force_autostart(dev); + } else { + /* + * Workaround for old versions without IWDG_SR_ONF bit: + * - write in IWDG_RLR_OFFSET + * - wait for sync + * - if sync succeeds, then iwdg is running + */ + writel(KR_KEY_EWA, priv->base + IWDG_KR); + rlr = readl(priv->base + IWDG_RLR); + writel(rlr, priv->base + IWDG_RLR); + ret = readl_poll_timeout(priv->base + IWDG_SR, sr, sr & SR_RVU, + TIMEOUT_US); + if (!ret) + wdt_set_force_autostart(dev); + + writel(KR_KEY_DWA, priv->base + IWDG_KR); + } + dev_dbg(dev, "IWDG init done\n"); return 0; diff --git a/drivers/watchdog/wdt-uclass.c b/drivers/watchdog/wdt-uclass.c index 10be334e9ed..b32590069d9 100644 --- a/drivers/watchdog/wdt-uclass.c +++ b/drivers/watchdog/wdt-uclass.c @@ -46,6 +46,15 @@ struct wdt_priv { struct cyclic_info cyclic; }; +int wdt_set_force_autostart(struct udevice *dev) +{ + struct wdt_priv *priv = dev_get_uclass_priv(dev); + + priv->autostart = true; + + return 0; +} + static void wdt_cyclic(struct cyclic_info *c) { struct wdt_priv *priv = container_of(c, struct wdt_priv, cyclic); |