diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index 79c715d4bce5..18510252db0d 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -48,6 +48,7 @@ zephyr_library_sources_ifdef(CONFIG_WDT_AMBIQ wdt_ambiq.c) zephyr_library_sources_ifdef(CONFIG_WDT_XMC4XXX wdt_xmc4xxx.c) zephyr_library_sources_ifdef(CONFIG_WWDT_NUMAKER wdt_wwdt_numaker.c) zephyr_library_sources_ifdef(CONFIG_WDT_ENE_KB1200 wdt_ene_kb1200.c) +zephyr_library_sources_ifdef(CONFIG_XILINX_WINDOW_WATCHDOG wdt_xilinx_wwdt.c) zephyr_library_sources_ifdef(CONFIG_WDT_DW wdt_dw.c wdt_dw_common.c) zephyr_library_sources_ifdef(CONFIG_WDT_INTEL_ADSP wdt_intel_adsp.c wdt_dw_common.c) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 0bc9652ebc93..ff87fb62c707 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -143,4 +143,6 @@ source "drivers/watchdog/Kconfig.ene" source "drivers/watchdog/Kconfig.litex" +source "drivers/watchdog/Kconfig.xilinx_wwdt" + endif # WATCHDOG diff --git a/drivers/watchdog/Kconfig.xilinx_wwdt b/drivers/watchdog/Kconfig.xilinx_wwdt new file mode 100644 index 000000000000..d0bd072cb98c --- /dev/null +++ b/drivers/watchdog/Kconfig.xilinx_wwdt @@ -0,0 +1,16 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +config XILINX_WINDOW_WATCHDOG + bool "Xilinx window watchdog driver" + default y + depends on DT_HAS_XLNX_VERSAL_WWDT_ENABLED + help + Enable Window watchdog driver for the versal_wwdt IP core. + Window watchdog timer(WWDT) contains closed(first) and + open(second) window with 32 bit width. Write to the watchdog + timer within predefined window periods of time. This means + a period that is not too soon and a period that is not too + late. The WWDT has to be restarted within the open window time. + If software tries to restart WWDT outside of the open window + time period, it generates a SOC reset. diff --git a/drivers/watchdog/wdt_xilinx_wwdt.c b/drivers/watchdog/wdt_xilinx_wwdt.c new file mode 100644 index 000000000000..f8ef9e9fac3d --- /dev/null +++ b/drivers/watchdog/wdt_xilinx_wwdt.c @@ -0,0 +1,274 @@ +/* + * Copyright (c) 2025 Advanced Micro Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT xlnx_versal_wwdt + +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(xilinx_wwdt, CONFIG_WDT_LOG_LEVEL); + +/* Register offsets for the WWDT device */ +#define XWWDT_MWR_OFFSET 0x00 +#define XWWDT_ESR_OFFSET 0x04 +#define XWWDT_FCR_OFFSET 0x08 +#define XWWDT_FWR_OFFSET 0x0c +#define XWWDT_SWR_OFFSET 0x10 + +/* Master Write Control Register Masks */ +#define XWWDT_MWR_MASK BIT(0) + +/* Enable and Status Register Masks */ +#define XWWDT_ESR_WINT_MASK BIT(16) +#define XWWDT_ESR_WSW_MASK BIT(8) +#define XWWDT_ESR_WEN_MASK BIT(0) + +/* Watchdog Second Window Shift */ +#define XWWDT_ESR_WSW_SHIFT 8U + +/* Maximum count value of each 32 bit window */ +#define XWWDT_MAX_COUNT_WINDOW GENMASK(31, 0) + +/* Maximum count value of closed and open window combined */ +#define XWWDT_MAX_COUNT_WINDOW_COMBINED GENMASK64(32, 1) + +struct xilinx_wwdt_config { + uint32_t wdt_clock_freq; + uint32_t timeout_sec; + mem_addr_t base; +}; + +struct xilinx_wwdt_data { + struct k_spinlock lock; + bool timeout_active; + bool wdt_started; +}; + +static int wdt_xilinx_wwdt_setup(const struct device *dev, uint8_t options) +{ + const struct xilinx_wwdt_config *config = dev->config; + struct xilinx_wwdt_data *data = dev->data; + uint32_t reg_value; + uint32_t ret = 0; + + k_spinlock_key_t key = k_spin_lock(&data->lock); + + if (!data->timeout_active) { + ret = -EINVAL; + goto out; + } + + if (data->wdt_started) { + ret = -EBUSY; + goto out; + } + + /* + * There is no control at driver level whether the WDT pauses in CPU sleep + * or when halted by debugger. Hence there is no check for the options. + */ + + /* Read enable status register and update WEN bit */ + reg_value = sys_read32(config->base + XWWDT_ESR_OFFSET) | XWWDT_ESR_WEN_MASK; + + /* Write enable status register with updated WEN value */ + sys_write32(reg_value, config->base + XWWDT_ESR_OFFSET); + data->wdt_started = true; +out: + k_spin_unlock(&data->lock, key); + return ret; +} + +static int wdt_xilinx_wwdt_install_timeout(const struct device *dev, + const struct wdt_timeout_cfg *cfg) +{ + const struct xilinx_wwdt_config *config = dev->config; + struct xilinx_wwdt_data *data = dev->data; + uint64_t closed_window_ms_count; + uint64_t open_window_ms_count; + uint64_t max_hw_timeout_ms; + uint64_t timeout_ms_count; + uint32_t timeout_ms; + uint64_t ms_count; + uint32_t ret = 0; + + k_spinlock_key_t key = k_spin_lock(&data->lock); + + if (data->wdt_started) { + ret = -EBUSY; + goto out; + } + + if (cfg->flags != WDT_FLAG_RESET_SOC) { + ret = -ENOTSUP; + goto out; + } + + timeout_ms = config->timeout_sec * 1000; + max_hw_timeout_ms = (XWWDT_MAX_COUNT_WINDOW_COMBINED * 1000) / config->wdt_clock_freq; + + /* + * If the maximum limit of the window is passed from the user space, + * overwrite the timeout with this value. + */ + if (cfg->window.max > 0) { + timeout_ms = cfg->window.max; + } + + /* Timeout greater than the maximum hardware timeout is invalid. */ + if (timeout_ms > max_hw_timeout_ms) { + ret = -EINVAL; + goto out; + } + + /* Calculate ticks for 1 milli-second */ + ms_count = (config->wdt_clock_freq) / 1000; + timeout_ms_count = timeout_ms * ms_count; + + closed_window_ms_count = cfg->window.min * ms_count; + if (closed_window_ms_count > XWWDT_MAX_COUNT_WINDOW) { + LOG_ERR("The closed window timeout is invalid."); + ret = -EINVAL; + goto out; + } + + open_window_ms_count = timeout_ms_count - closed_window_ms_count; + if (open_window_ms_count > XWWDT_MAX_COUNT_WINDOW) { + LOG_ERR("The open window timeout is invalid."); + ret = -EINVAL; + goto out; + } + + sys_write32(XWWDT_MWR_MASK, config->base + XWWDT_MWR_OFFSET); + sys_write32(~(uint32_t)XWWDT_ESR_WEN_MASK, config->base + XWWDT_ESR_OFFSET); + sys_write32(closed_window_ms_count, config->base + XWWDT_FWR_OFFSET); + sys_write32(open_window_ms_count, config->base + XWWDT_SWR_OFFSET); + + data->timeout_active = true; +out: + k_spin_unlock(&data->lock, key); + return ret; +} + +static int wdt_xilinx_wwdt_feed(const struct device *dev, int channel_id) +{ + const struct xilinx_wwdt_config *config = dev->config; + struct xilinx_wwdt_data *data = dev->data; + uint32_t control_status_reg; + uint32_t is_sec_window; + uint32_t ret = 0; + + k_spinlock_key_t key = k_spin_lock(&data->lock); + + if (channel_id != 0 || !data->timeout_active) { + ret = -EINVAL; + goto out; + } + + /* Enable write access control bit for the WWDT. */ + sys_write32(XWWDT_MWR_MASK, config->base + XWWDT_MWR_OFFSET); + + /* Trigger restart kick to WWDT. */ + control_status_reg = sys_read32(config->base + XWWDT_ESR_OFFSET); + + /* Check if WWDT is in Second window. */ + is_sec_window = (control_status_reg & (uint32_t)XWWDT_ESR_WSW_MASK) >> XWWDT_ESR_WSW_SHIFT; + + if (is_sec_window != 1) { + LOG_ERR("Feed in Closed window is not supported."); + ret = -ENOTSUP; + goto out; + } + + control_status_reg |= (uint32_t)XWWDT_ESR_WSW_MASK; + sys_write32(control_status_reg, config->base + XWWDT_ESR_OFFSET); +out: + k_spin_unlock(&data->lock, key); + return ret; +} + +static int wdt_xilinx_wwdt_disable(const struct device *dev) +{ + const struct xilinx_wwdt_config *config = dev->config; + struct xilinx_wwdt_data *data = dev->data; + uint32_t is_wwdt_enable; + uint32_t is_sec_window; + uint32_t reg_value; + uint32_t ret = 0; + + k_spinlock_key_t key = k_spin_lock(&data->lock); + + is_wwdt_enable = sys_read32(config->base + XWWDT_ESR_OFFSET) & XWWDT_ESR_WEN_MASK; + + if (is_wwdt_enable == 0) { + ret = -EFAULT; + goto out; + } + + /* Read enable status register and check if WWDT is in open window. */ + is_sec_window = (sys_read32(config->base + XWWDT_ESR_OFFSET) & XWWDT_ESR_WSW_MASK) >> + XWWDT_ESR_WSW_SHIFT; + + if (is_sec_window != 1) { + LOG_ERR("Disabling WWDT in closed window is not allowed."); + ret = -EPERM; + goto out; + } + + /* Read enable status register and update WEN bit. */ + reg_value = sys_read32(config->base + XWWDT_ESR_OFFSET) & (~XWWDT_ESR_WEN_MASK); + + /* Set WSW bit to zero. It is RW1C bit. */ + reg_value &= ~((uint32_t)XWWDT_ESR_WSW_MASK); + + /* Write enable status register with updated WEN and WSW value. */ + sys_write32(reg_value, config->base + XWWDT_ESR_OFFSET); + + data->wdt_started = false; +out: + k_spin_unlock(&data->lock, key); + return ret; +} + +static int wdt_xilinx_wwdt_init(const struct device *dev) +{ + const struct xilinx_wwdt_config *config = dev->config; + uint32_t ret = 0; + + if (config->timeout_sec == 0) { + return -EINVAL; + } + + return ret; +} + +static DEVICE_API(wdt, wdt_xilinx_wwdt_api) = { + .setup = wdt_xilinx_wwdt_setup, + .install_timeout = wdt_xilinx_wwdt_install_timeout, + .feed = wdt_xilinx_wwdt_feed, + .disable = wdt_xilinx_wwdt_disable, +}; + +#define WDT_XILINX_WWDT_INIT(inst) \ + static struct xilinx_wwdt_data wdt_xilinx_wwdt_##inst##_dev_data; \ + \ + static const struct xilinx_wwdt_config wdt_xilinx_wwdt_##inst##_cfg = { \ + .base = DT_INST_REG_ADDR(inst), \ + .timeout_sec = DT_INST_PROP(inst, timeout_sec), \ + .wdt_clock_freq = DT_INST_PROP_BY_PHANDLE(inst, clocks, clock_frequency), \ + }; \ + \ + DEVICE_DT_INST_DEFINE(inst, &wdt_xilinx_wwdt_init, NULL, \ + &wdt_xilinx_wwdt_##inst##_dev_data, \ + &wdt_xilinx_wwdt_##inst##_cfg, PRE_KERNEL_1, \ + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_xilinx_wwdt_api); + +DT_INST_FOREACH_STATUS_OKAY(WDT_XILINX_WWDT_INIT) diff --git a/dts/bindings/watchdog/xlnx,versal-wwdt.yaml b/dts/bindings/watchdog/xlnx,versal-wwdt.yaml new file mode 100644 index 000000000000..2754554b1e1d --- /dev/null +++ b/dts/bindings/watchdog/xlnx,versal-wwdt.yaml @@ -0,0 +1,21 @@ +# Copyright (c) 2025 Advanced Micro Devices, Inc. +# SPDX-License-Identifier: Apache-2.0 + +description: Xilinx window watchdog + +compatible: "xlnx,versal-wwdt" + +include: base.yaml + +properties: + reg: + required: true + + timeout-sec: + type: int + required: true + description: Timeout value in seconds. + + clocks: + required: true + description: Reference to clock for window watchdog core.