drivers: watchdog: add Xilinx AXI Timebase WDT driver
Add a driver for the Xilinx AXI Timebase WDT logic core. This can be instantiated on various Xilinx FPGA-based platforms such as the Digilent Arty, although it is not part of the default image used with the Zephyr board configuration. The driver can also optionally implement the HWINFO API to allow determining whether the last system reset was initiated by the WDT. Since this is a standalone IP core which could be used a variety of configurations, this support is optional in case the system/SoC it is used with already implements this support. Signed-off-by: Robert Hancock <robert.hancock@calian.com>
This commit is contained in:
parent
f271a8220d
commit
e57bab59ff
|
@ -32,6 +32,7 @@ zephyr_library_sources_ifdef(CONFIG_WDT_COUNTER wdt_counter.c)
|
|||
zephyr_library_sources_ifdef(CONFIG_WDT_NXP_S32 wdt_nxp_s32.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_SMARTBOND wdt_smartbond.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_TI_TPS382X wdt_ti_tps382x.c)
|
||||
zephyr_library_sources_ifdef(CONFIG_WDT_XILINX_AXI wdt_xilinx_axi.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)
|
||||
|
|
|
@ -104,4 +104,6 @@ source "drivers/watchdog/Kconfig.ti_tps382x"
|
|||
|
||||
source "drivers/watchdog/Kconfig.tco"
|
||||
|
||||
source "drivers/watchdog/Kconfig.xlnx"
|
||||
|
||||
endif # WATCHDOG
|
||||
|
|
25
drivers/watchdog/Kconfig.xlnx
Normal file
25
drivers/watchdog/Kconfig.xlnx
Normal file
|
@ -0,0 +1,25 @@
|
|||
# Xilinx watchdog configuration
|
||||
|
||||
# Copyright (c) 2023, Calian
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config WDT_XILINX_AXI
|
||||
bool "Xilinx AXI Timebase WDT driver"
|
||||
default y
|
||||
depends on DT_HAS_XLNX_XPS_TIMEBASE_WDT_1_00_A_ENABLED
|
||||
help
|
||||
Enable the Xilinx AXI Timebase WDT driver.
|
||||
|
||||
if WDT_XILINX_AXI
|
||||
|
||||
config WDT_XILINX_AXI_HWINFO_API
|
||||
bool "Expose HWINFO API in Xilinx AXI Timebase WDT driver"
|
||||
default y
|
||||
select HWINFO
|
||||
help
|
||||
Controls whether the Xilinx AXI Timebase WDT driver exposes a HWINFO
|
||||
API which allows determining whether the WDT initiated the last
|
||||
system reset. This may need to be disabled if using a device or SoC
|
||||
which already implements this API.
|
||||
|
||||
endif # WDT_XILINX_AXI
|
287
drivers/watchdog/wdt_xilinx_axi.c
Normal file
287
drivers/watchdog/wdt_xilinx_axi.c
Normal file
|
@ -0,0 +1,287 @@
|
|||
/*
|
||||
* Driver for Xilinx AXI Timebase WDT core, as described in
|
||||
* Xilinx document PG128.
|
||||
*
|
||||
* Note that the window mode of operation of the core is
|
||||
* currently not supported. Also, only a full SoC reset is
|
||||
* supported as a watchdog expiry action.
|
||||
*
|
||||
* Copyright © 2023 Calian Ltd. All rights reserved.
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#define DT_DRV_COMPAT xlnx_xps_timebase_wdt_1_00_a
|
||||
|
||||
#include <errno.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/drivers/watchdog.h>
|
||||
#include <zephyr/drivers/hwinfo.h>
|
||||
|
||||
enum xilinx_wdt_axi_register {
|
||||
REG_TWCSR0 = 0x00, /* Control/Status Register 0 */
|
||||
REG_TWCSR1 = 0x04, /* Control/Status Register 1 */
|
||||
REG_TBR = 0x08, /* Timebase Register */
|
||||
REG_MWR = 0x0C, /* Master Write Control Register */
|
||||
};
|
||||
|
||||
enum xilinx_wdt_csr0_bits {
|
||||
CSR0_WRS = BIT(3),
|
||||
CSR0_WDS = BIT(2),
|
||||
CSR0_EWDT1 = BIT(1),
|
||||
CSR0_EWDT2 = BIT(0),
|
||||
};
|
||||
|
||||
enum xilinx_wdt_csr1_bits {
|
||||
CSR1_EWDT2 = BIT(0),
|
||||
};
|
||||
|
||||
enum {
|
||||
TIMER_WIDTH_MIN = 8,
|
||||
};
|
||||
|
||||
LOG_MODULE_REGISTER(wdt_xilinx_axi, CONFIG_WDT_LOG_LEVEL);
|
||||
|
||||
struct xilinx_wdt_axi_config {
|
||||
mem_addr_t base;
|
||||
uint32_t clock_rate;
|
||||
uint8_t timer_width_max;
|
||||
bool enable_once;
|
||||
};
|
||||
|
||||
struct xilinx_wdt_axi_data {
|
||||
struct k_spinlock lock;
|
||||
bool timeout_active;
|
||||
bool wdt_started;
|
||||
};
|
||||
|
||||
static const struct device *first_wdt_dev;
|
||||
|
||||
static int wdt_xilinx_axi_setup(const struct device *dev, uint8_t options)
|
||||
{
|
||||
const struct xilinx_wdt_axi_config *config = dev->config;
|
||||
struct xilinx_wdt_axi_data *data = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
||||
int ret;
|
||||
|
||||
if (!data->timeout_active) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (data->wdt_started) {
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* We don't actually know or control at the driver level whether
|
||||
* the WDT pauses in CPU sleep or when halted by the debugger,
|
||||
* so we don't check anything with the options.
|
||||
*/
|
||||
sys_write32(CSR0_EWDT1 | CSR0_WDS, config->base + REG_TWCSR0);
|
||||
sys_write32(CSR1_EWDT2, config->base + REG_TWCSR1);
|
||||
data->wdt_started = true;
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
k_spin_unlock(&data->lock, key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wdt_xilinx_axi_disable(const struct device *dev)
|
||||
{
|
||||
const struct xilinx_wdt_axi_config *config = dev->config;
|
||||
struct xilinx_wdt_axi_data *data = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
||||
int ret;
|
||||
|
||||
if (config->enable_once) {
|
||||
ret = -EPERM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!data->wdt_started) {
|
||||
ret = -EFAULT;
|
||||
goto out;
|
||||
}
|
||||
|
||||
sys_write32(CSR0_WDS, config->base + REG_TWCSR0);
|
||||
sys_write32(0, config->base + REG_TWCSR1);
|
||||
data->wdt_started = false;
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
k_spin_unlock(&data->lock, key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wdt_xilinx_axi_install_timeout(const struct device *dev,
|
||||
const struct wdt_timeout_cfg *cfg)
|
||||
{
|
||||
const struct xilinx_wdt_axi_config *config = dev->config;
|
||||
struct xilinx_wdt_axi_data *data = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
||||
uint32_t timer_width;
|
||||
bool good_timer_width = false;
|
||||
int ret;
|
||||
|
||||
if (data->timeout_active) {
|
||||
ret = -ENOMEM;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (!(cfg->flags & WDT_FLAG_RESET_SOC)) {
|
||||
ret = -ENOTSUP;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (cfg->window.min != 0) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
for (timer_width = TIMER_WIDTH_MIN; timer_width <= config->timer_width_max; timer_width++) {
|
||||
/* Note: WDT expiry happens after 2 wraps of the timer (first raises an interrupt
|
||||
* which is not used, second triggers a reset) so add 1 to timer_width.
|
||||
*/
|
||||
const uint64_t expiry_cycles = ((uint64_t)1) << (timer_width + 1);
|
||||
const uint64_t expiry_msec = expiry_cycles * 1000 / config->clock_rate;
|
||||
|
||||
if (expiry_msec >= cfg->window.max) {
|
||||
LOG_INF("Set timer width to %u, actual expiry %u msec", timer_width,
|
||||
(unsigned int)expiry_msec);
|
||||
good_timer_width = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!good_timer_width) {
|
||||
LOG_ERR("Cannot support timeout value of %u msec", cfg->window.max);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
sys_write32(timer_width, config->base + REG_MWR);
|
||||
data->timeout_active = true;
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
k_spin_unlock(&data->lock, key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wdt_xilinx_axi_feed(const struct device *dev, int channel_id)
|
||||
{
|
||||
const struct xilinx_wdt_axi_config *config = dev->config;
|
||||
struct xilinx_wdt_axi_data *data = dev->data;
|
||||
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
||||
uint32_t twcsr0 = sys_read32(config->base + REG_TWCSR0);
|
||||
int ret;
|
||||
|
||||
if (channel_id != 0 || !data->timeout_active) {
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
twcsr0 |= CSR0_WDS;
|
||||
if (data->wdt_started) {
|
||||
twcsr0 |= CSR0_EWDT1;
|
||||
}
|
||||
|
||||
sys_write32(twcsr0, config->base + REG_TWCSR0);
|
||||
ret = 0;
|
||||
|
||||
out:
|
||||
k_spin_unlock(&data->lock, key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wdt_xilinx_axi_init(const struct device *dev)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_WDT_XILINX_AXI_HWINFO_API)) {
|
||||
if (first_wdt_dev) {
|
||||
LOG_WRN("Multiple WDT instances, only first will implement HWINFO");
|
||||
} else {
|
||||
first_wdt_dev = dev;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_WDT_XILINX_AXI_HWINFO_API
|
||||
|
||||
int z_impl_hwinfo_get_reset_cause(uint32_t *cause)
|
||||
{
|
||||
if (!first_wdt_dev) {
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
const struct xilinx_wdt_axi_config *config = first_wdt_dev->config;
|
||||
|
||||
if ((sys_read32(config->base + REG_TWCSR0) & CSR0_WRS) != 0) {
|
||||
*cause = RESET_WATCHDOG;
|
||||
} else {
|
||||
*cause = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int z_impl_hwinfo_clear_reset_cause(void)
|
||||
{
|
||||
if (!first_wdt_dev) {
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
const struct xilinx_wdt_axi_config *config = first_wdt_dev->config;
|
||||
struct xilinx_wdt_axi_data *data = first_wdt_dev->data;
|
||||
|
||||
k_spinlock_key_t key = k_spin_lock(&data->lock);
|
||||
uint32_t twcsr0 = sys_read32(config->base + REG_TWCSR0);
|
||||
|
||||
if ((twcsr0 & CSR0_WRS) != 0) {
|
||||
twcsr0 |= CSR0_WRS;
|
||||
sys_write32(twcsr0, config->base + REG_TWCSR0);
|
||||
}
|
||||
|
||||
k_spin_unlock(&data->lock, key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int z_impl_hwinfo_get_supported_reset_cause(uint32_t *supported)
|
||||
{
|
||||
if (!first_wdt_dev) {
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
*supported = RESET_WATCHDOG;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static const struct wdt_driver_api wdt_xilinx_api = {
|
||||
.setup = wdt_xilinx_axi_setup,
|
||||
.disable = wdt_xilinx_axi_disable,
|
||||
.install_timeout = wdt_xilinx_axi_install_timeout,
|
||||
.feed = wdt_xilinx_axi_feed,
|
||||
};
|
||||
|
||||
#define WDT_XILINX_AXI_INIT(inst) \
|
||||
static struct xilinx_wdt_axi_data wdt_xilinx_axi_##inst##_dev_data; \
|
||||
\
|
||||
static const struct xilinx_wdt_axi_config wdt_xilinx_axi_##inst##_cfg = { \
|
||||
.base = DT_INST_REG_ADDR(inst), \
|
||||
.clock_rate = DT_INST_PROP_BY_PHANDLE(inst, clocks, clock_frequency), \
|
||||
.timer_width_max = DT_INST_PROP(inst, xlnx_wdt_interval), \
|
||||
.enable_once = DT_INST_PROP(inst, xlnx_wdt_enable_once), \
|
||||
}; \
|
||||
\
|
||||
DEVICE_DT_INST_DEFINE(inst, &wdt_xilinx_axi_init, NULL, &wdt_xilinx_axi_##inst##_dev_data, \
|
||||
&wdt_xilinx_axi_##inst##_cfg, PRE_KERNEL_1, \
|
||||
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_xilinx_api);
|
||||
|
||||
DT_INST_FOREACH_STATUS_OKAY(WDT_XILINX_AXI_INIT)
|
Loading…
Reference in a new issue