diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index 1e496d83fe6..b96db0278cd 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -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) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 2636ef40178..8bec6eed73a 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -104,4 +104,6 @@ source "drivers/watchdog/Kconfig.ti_tps382x" source "drivers/watchdog/Kconfig.tco" +source "drivers/watchdog/Kconfig.xlnx" + endif # WATCHDOG diff --git a/drivers/watchdog/Kconfig.xlnx b/drivers/watchdog/Kconfig.xlnx new file mode 100644 index 00000000000..2a07d69319b --- /dev/null +++ b/drivers/watchdog/Kconfig.xlnx @@ -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 diff --git a/drivers/watchdog/wdt_xilinx_axi.c b/drivers/watchdog/wdt_xilinx_axi.c new file mode 100644 index 00000000000..f4f6b2e2a14 --- /dev/null +++ b/drivers/watchdog/wdt_xilinx_axi.c @@ -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 +#include +#include +#include +#include +#include + +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)