blob: 94422b638f385187eab7328a6501e4af90e83a8a [file] [log] [blame]
/*
* 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;
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;
int 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;
int 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 = cfg->window.max;
max_hw_timeout_ms = (XWWDT_MAX_COUNT_WINDOW_COMBINED * 1000) / config->wdt_clock_freq;
/* 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;
int 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;
int 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);
/* 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;
int ret = 0;
if (config->wdt_clock_freq == 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), \
.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)