Skip to content

Commit

Permalink
drivers: watchdog: Add Xilinx Window Watchdog driver support
Browse files Browse the repository at this point in the history
Xilinx Window Watchdog driver uses window watchdog mode. Window watchdog
timer(WWDT) contains closed(first) and open(second) window with 32bit
width each. 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 the software tries to restart WWDT outside of open window time
period, it generates a SoC reset.

Signed-off-by: Harini T <[email protected]>
  • Loading branch information
Harini T committed Feb 27, 2025
1 parent d94485d commit 56ea70b
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 0 deletions.
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,
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)

0 comments on commit 56ea70b

Please sign in to comment.