diff options
Diffstat (limited to 'drivers/watchdog')
-rw-r--r-- | drivers/watchdog/Kconfig | 19 | ||||
-rw-r--r-- | drivers/watchdog/Makefile | 2 | ||||
-rw-r--r-- | drivers/watchdog/at91sam9_wdt.c | 55 | ||||
-rw-r--r-- | drivers/watchdog/qcom-wdt.c | 137 | ||||
-rw-r--r-- | drivers/watchdog/renesas_wdt.c | 189 |
5 files changed, 385 insertions, 17 deletions
diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1bb67f50352..e9ea874d0e3 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -27,8 +27,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 +336,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/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..adbb5aacdc3 --- /dev/null +++ b/drivers/watchdog/qcom-wdt.c @@ -0,0 +1,137 @@ +// 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)); + 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 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), +}; |