Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Xilinx Window Watchdog driver #86430

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions drivers/watchdog/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions drivers/watchdog/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -143,4 +143,6 @@ source "drivers/watchdog/Kconfig.ene"

source "drivers/watchdog/Kconfig.litex"

source "drivers/watchdog/Kconfig.xilinx_wwdt"

endif # WATCHDOG
16 changes: 16 additions & 0 deletions drivers/watchdog/Kconfig.xilinx_wwdt
Original file line number Diff line number Diff line change
@@ -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.
274 changes: 274 additions & 0 deletions drivers/watchdog/wdt_xilinx_wwdt.c
Original file line number Diff line number Diff line change
@@ -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 <errno.h>
#include <zephyr/device.h>
#include <zephyr/kernel.h>
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/drivers/watchdog.h>
#include <zephyr/logging/log.h>
#include <zephyr/sys/util.h>

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,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, a minor query. I see that there is no callback registered and no interrupt available after timeout. When the closed window time is given some time by the user, how will the user understand that the window is open, so that they can clear the timer.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ghostMan-pac,

I see that there is no callback registered and no interrupt available after timeout.
The WWDT error interrupts (IRQs) occur when the watchdog timer is not serviced within the predefined window periods. These IRQs are routed to the Processing System Manager (PSM) error accumulator module. The PSM is responsible for managing power and system-level errors, generating a System on Chip (SoC) reset when a WWDT error occurs. The system reset event is signaled as a system error for the PSM firmware to handle and a reset output signal to the MIO/EMIO. Refer DT-Bindings.

When the closed window time is given some time by the user, how will the user understand that the window is open, so that they can clear the timer.
In general, the feed operation is independent of whether the window is open or closed. During configuration, the user specifies the closed window value. If the user attempts to ping during the closed window, the driver will produce an error. Once the wdt is in second window, the feed works.

Consider an example in which window.max is set to 10 seconds and window.min is set to 5 seconds. If the user tries to feed/ping every 4 seconds, the first ping will not be allowed by driver as application tries to ping in first window. The next feed after 4 seconds (at 8th second) will be allowed by driver to restart the timer.

Please refer

for (int i = 0; i < WDT_FEED_TRIES; ++i) {
printk("Feeding watchdog...\n");
wdt_feed(wdt, wdt_channel_id);
k_sleep(K_MSEC(WDG_FEED_INTERVAL));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @Harini-T for the detailed explanation. I have seen another watchdog peripheral which will reset the board, if pinged within the closed window state. That is why I asked whether there is any option for the peripheral to indicate to the user whether the window is opened or not. It seems your watchdog does not take the drastic measure of resetting the board, if the pinging is early (before window open).

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)
Loading