From 710fb596dfbef514bf32835b8c0cbdc3fed8fd61 Mon Sep 17 00:00:00 2001 From: The Nguyen Date: Wed, 25 Dec 2024 06:16:37 +0000 Subject: [PATCH] drivers: watchdog: initial support for watchdog driver on Renesas RA family First commit to add support for Renesas RA watchdog driver Signed-off-by: The Nguyen --- drivers/watchdog/CMakeLists.txt | 1 + drivers/watchdog/Kconfig | 2 + drivers/watchdog/Kconfig.renesas_ra | 29 ++ drivers/watchdog/wdt_renesas_ra.c | 332 ++++++++++++++++++++++ dts/bindings/watchdog/renesas,ra-wdt.yaml | 15 + modules/Kconfig.renesas_fsp | 5 + 6 files changed, 384 insertions(+) create mode 100644 drivers/watchdog/Kconfig.renesas_ra create mode 100644 drivers/watchdog/wdt_renesas_ra.c create mode 100644 dts/bindings/watchdog/renesas,ra-wdt.yaml diff --git a/drivers/watchdog/CMakeLists.txt b/drivers/watchdog/CMakeLists.txt index 38b224e38a2..79a5649b839 100644 --- a/drivers/watchdog/CMakeLists.txt +++ b/drivers/watchdog/CMakeLists.txt @@ -56,5 +56,6 @@ zephyr_library_sources_ifdef(CONFIG_WDT_INTEL_ADSP wdt_intel_adsp.c wdt_dw_commo zephyr_library_sources_ifdef(CONFIG_WDT_ANDES_ATCWDT200 wdt_andes_atcwdt200.c) zephyr_library_sources_ifdef(CONFIG_WDT_NXP_FS26 wdt_nxp_fs26.c) zephyr_library_sources_ifdef(CONFIG_WDT_SHELL wdt_shell.c) +zephyr_library_sources_ifdef(CONFIG_WDT_RENESAS_RA wdt_renesas_ra.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE wdt_handlers.c) diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 1fb98e81a0c..30d623e22ad 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -147,4 +147,6 @@ source "drivers/watchdog/Kconfig.litex" source "drivers/watchdog/Kconfig.rts5912" +source "drivers/watchdog/Kconfig.renesas_ra" + endif # WATCHDOG diff --git a/drivers/watchdog/Kconfig.renesas_ra b/drivers/watchdog/Kconfig.renesas_ra new file mode 100644 index 00000000000..1a5bbeb90b2 --- /dev/null +++ b/drivers/watchdog/Kconfig.renesas_ra @@ -0,0 +1,29 @@ +# Copyright (c) 2025 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +config WDT_RENESAS_RA + bool "Watchdog Driver for Renesas RA family" + default y + depends on DT_HAS_RENESAS_RA_WDT_ENABLED + select HAS_WDT_DISABLE_AT_BOOT + select USE_RA_FSP_WDT + help + Enable watchdog driver for Renesas RA MCUs + +if WDT_RENESAS_RA + +config WDT_RENESAS_RA_NMI + bool "NMI interrupt enable" + default y + select RUNTIME_NMI + help + Watchdog timer generates NMI at value 0 + +config WDT_RENESAS_RA_START_IN_BOOT + bool "Start WDT in boot time" + default y + depends on !HAS_WDT_DISABLE_AT_BOOT + help + Start watchdog in boot time + +endif # WDT_RENESAS_RA diff --git a/drivers/watchdog/wdt_renesas_ra.c b/drivers/watchdog/wdt_renesas_ra.c new file mode 100644 index 00000000000..78790b9ccc0 --- /dev/null +++ b/drivers/watchdog/wdt_renesas_ra.c @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2025 Renesas Electronics Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include "r_wdt.h" + +LOG_MODULE_REGISTER(wdt_renesas_ra, CONFIG_WDT_LOG_LEVEL); + +struct wdt_renesas_ra_config { + const struct device *clock_dev; + const struct clock_control_ra_subsys_cfg clock_subsys; +}; + +struct wdt_renesas_ra_data { + struct wdt_timeout_cfg timeout; + struct st_wdt_instance_ctrl wdt_ctrl; + struct st_wdt_cfg wdt_cfg; + struct k_mutex inst_lock; + atomic_t device_state; +}; + +#define WDT_RENESAS_RA_ATOMIC_ENABLE (0) +#define WDT_RENESAS_RA_ATOMIC_TIMEOUT_SET (1) + +/* Lookup table for WDT period raw cycle */ +const float timeout_period_lut[] = { + [WDT_TIMEOUT_128] = 128, [WDT_TIMEOUT_512] = 512, [WDT_TIMEOUT_1024] = 1024, + [WDT_TIMEOUT_2048] = 2048, [WDT_TIMEOUT_4096] = 4096, [WDT_TIMEOUT_8192] = 8192, + [WDT_TIMEOUT_16384] = 16384}; + +/* Lookup table for the division value of the input clock count */ +const float clock_div_lut[] = {[WDT_CLOCK_DIVISION_1] = 1, [WDT_CLOCK_DIVISION_4] = 4, + [WDT_CLOCK_DIVISION_16] = 16, [WDT_CLOCK_DIVISION_32] = 32, + [WDT_CLOCK_DIVISION_64] = 64, [WDT_CLOCK_DIVISION_128] = 128, + [WDT_CLOCK_DIVISION_256] = 256, [WDT_CLOCK_DIVISION_512] = 512, + [WDT_CLOCK_DIVISION_2048] = 2048, [WDT_CLOCK_DIVISION_8192] = 8192}; + +#define WDT_WINDOW_INVALID (-1) + +/* Lookup table for the window start position setting */ +const int window_start_lut[] = { + [0] = WDT_WINDOW_START_100, [1] = WDT_WINDOW_START_75, [2] = WDT_WINDOW_START_50, + [3] = WDT_WINDOW_START_25, [4] = WDT_WINDOW_INVALID, +}; + +/* Lookup table for the window end position setting */ +const int window_end_lut[] = { + [0] = WDT_WINDOW_INVALID, [1] = WDT_WINDOW_END_75, [2] = WDT_WINDOW_END_50, + [3] = WDT_WINDOW_END_25, [4] = WDT_WINDOW_END_0, +}; + +static inline void wdt_renesas_ra_inst_lock(const struct device *dev) +{ + struct wdt_renesas_ra_data *data = dev->data; + + k_mutex_lock(&data->inst_lock, K_FOREVER); +} + +static inline void wdt_renesas_ra_inst_unlock(const struct device *dev) +{ + struct wdt_renesas_ra_data *data = dev->data; + + k_mutex_unlock(&data->inst_lock); +} + +static int wdt_renesas_ra_timeout_calculate(const struct device *dev, + const struct wdt_timeout_cfg *config) +{ + struct wdt_renesas_ra_data *data = dev->data; + const struct wdt_renesas_ra_config *cfg = dev->config; + struct st_wdt_cfg *p_cfg = &data->wdt_cfg; + unsigned int window_start_idx; + unsigned int window_end_idx; + unsigned int best_divisor = WDT_CLOCK_DIVISION_1; + unsigned int best_timeout = WDT_TIMEOUT_128; + unsigned int best_period_ms = UINT_MAX; + unsigned int min_delta = UINT_MAX; + uint32_t clock_rate; + int ret; + + if (atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_TIMEOUT_SET)) { + if (config->window.min != data->timeout.window.min || + config->window.max != data->timeout.window.max || + config->flags != data->timeout.flags) { + LOG_ERR("wdt support only one timeout setting value"); + return -EINVAL; + } + + data->timeout.callback = config->callback; + return 0; + } + + ret = clock_control_get_rate(cfg->clock_dev, (clock_control_subsys_t)&cfg->clock_subsys, + &clock_rate); + if (unlikely(ret)) { + return ret; + } + + for (unsigned int divisor = WDT_CLOCK_DIVISION_1; divisor < ARRAY_SIZE(clock_div_lut); + divisor++) { + for (unsigned int timeout = WDT_TIMEOUT_128; + timeout < ARRAY_SIZE(timeout_period_lut); timeout++) { + unsigned int period_ms = + (unsigned int)(1000.0F * clock_div_lut[divisor] * + timeout_period_lut[timeout] / (float)clock_rate); + unsigned int delta = period_ms > config->window.max + ? period_ms - config->window.max + : config->window.max - period_ms; + + if (delta < min_delta) { + min_delta = delta; + best_divisor = divisor; + best_timeout = timeout; + best_period_ms = period_ms; + } + } + } + + if (min_delta == UINT_MAX) { + LOG_ERR("wdt timeout out of range"); + return -EINVAL; + } + + window_start_idx = (config->window.min * 4 + best_period_ms - 1) / best_period_ms; + window_end_idx = (config->window.max * 4 + best_period_ms - 1) / best_period_ms; + + if (window_start_lut[window_start_idx] == WDT_WINDOW_INVALID || + window_end_lut[window_end_idx] == WDT_WINDOW_INVALID) { + LOG_ERR("this wdt timeout is not supported"); + return -ENOTSUP; + } + + p_cfg->clock_division = (wdt_clock_division_t)best_divisor; + p_cfg->timeout = (wdt_timeout_t)best_timeout; + p_cfg->window_start = (wdt_window_start_t)window_start_lut[window_start_idx]; + p_cfg->window_end = (wdt_window_end_t)window_end_lut[window_end_idx]; + + LOG_INF("actual window min = %.2f ms", window_start_idx * best_period_ms * 0.25); + LOG_INF("actual window max = %.2f ms", window_end_idx * best_period_ms * 0.25); + + memcpy(&data->timeout, config, sizeof(struct wdt_timeout_cfg)); + + return 0; +} + +static int wdt_renesas_ra_setup(const struct device *dev, uint8_t options) +{ + struct wdt_renesas_ra_data *data = dev->data; + int ret = 0; + + /* + * TODO: WDT must be restarted with wdt_feed call after sleep -> will be added later when PM + * mode is supported + */ + if ((options & WDT_OPT_PAUSE_IN_SLEEP) != 0) { + LOG_ERR("wdt pause in sleep mode not supported"); + return -ENOTSUP; + } + + wdt_renesas_ra_inst_lock(dev); + + if (atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE)) { + LOG_ERR("wdt has been already setup"); + ret = -EBUSY; + goto end; + } + + if (!atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_TIMEOUT_SET)) { + LOG_ERR("wdt timeout should be installed before"); + ret = -EFAULT; + goto end; + } + + /* Pause watchdog timer when CPU is halted by the debugger. */ + R_DEBUG->DBGSTOPCR_b.DBGSTOP_WDT = (options & WDT_OPT_PAUSE_HALTED_BY_DBG) != 0 ? 1 : 0; + + if (R_WDT_Open(&data->wdt_ctrl, &data->wdt_cfg) != FSP_SUCCESS) { + LOG_ERR("wdt setup failed"); + ret = -EIO; + goto end; + } + + if (R_WDT_Refresh(&data->wdt_ctrl) != FSP_SUCCESS) { + LOG_ERR("wdt start failed"); + ret = -EIO; + goto end; + } + + atomic_set_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE); + +end: + wdt_renesas_ra_inst_unlock(dev); + + return ret; +} + +static int wdt_renesas_ra_disable(const struct device *dev) +{ + struct wdt_renesas_ra_data *data = dev->data; + + if (!atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE)) { + LOG_ERR("wdt has not been enabled yet"); + return -EFAULT; + } + + LOG_ERR("wdt can not be stopped once it has started"); + return -EPERM; +} + +#ifdef CONFIG_WDT_RENESAS_RA_NMI +void wdt_renesas_ra_callback_adapter(wdt_callback_args_t *p_args) +{ + const struct device *dev = p_args->p_context; + struct wdt_renesas_ra_data *data = dev->data; + wdt_callback_t callback = data->timeout.callback; + + if (callback != NULL) { + callback(dev, 0); + } +} +#endif /* CONFIG_WDT_RENESAS_RA_NMI */ + +#define WDT_RENESAS_RA_SUPPORTED_FLAGS (WDT_FLAG_RESET_NONE | WDT_FLAG_RESET_SOC) + +static int wdt_renesas_ra_install_timeout(const struct device *dev, + const struct wdt_timeout_cfg *config) +{ + struct wdt_renesas_ra_data *data = dev->data; + struct st_wdt_cfg *p_config = &data->wdt_cfg; + int ret = 0; + + if (config->window.min > config->window.max || config->window.max == 0) { + return -EINVAL; + } + + if ((config->flags & ~WDT_RENESAS_RA_SUPPORTED_FLAGS) != 0) { + return -ENOTSUP; + } + + if (config->callback == NULL && (config->flags & WDT_FLAG_RESET_MASK) == 0) { + LOG_ERR("no timeout response was chosen"); + return -EINVAL; + } + + if (config->callback != NULL && (config->flags & WDT_FLAG_RESET_MASK) != 0) { + LOG_ERR("WDT_FLAG_RESET_NONE should be chosen in case of interrupt response"); + return -ENOTSUP; + } + + wdt_renesas_ra_inst_lock(dev); + + if (atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE)) { + LOG_ERR("cannot change timeout settings after wdt setup"); + ret = -EBUSY; + goto end; + } + +#ifndef CONFIG_WDT_RENESAS_RA_NMI + if (config->callback != NULL) { + LOG_ERR("interrupt response only available in case CONFIG_WDT_RENESAS_RA_NMI=y"); + ret = -ENOTSUP; + goto end; + } +#else + p_config->reset_control = (config->flags & WDT_FLAG_RESET_MASK) != 0 + ? WDT_RESET_CONTROL_RESET + : WDT_RESET_CONTROL_NMI; +#endif + + ret = wdt_renesas_ra_timeout_calculate(dev, config); + if (ret < 0) { + goto end; + } + + atomic_set_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_TIMEOUT_SET); + LOG_INF("wdt timeout was set successfully"); + +end: + wdt_renesas_ra_inst_unlock(dev); + + return ret; +} + +static int wdt_renesas_ra_feed(const struct device *dev, int channel_id) +{ + struct wdt_renesas_ra_data *data = dev->data; + + if (!atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE) || + channel_id != 0) { + return -EINVAL; + } + + if (R_WDT_Refresh(&data->wdt_ctrl) != FSP_SUCCESS) { + return -EIO; + } + + return 0; +} + +static DEVICE_API(wdt, wdt_renesas_ra_api) = { + .setup = wdt_renesas_ra_setup, + .disable = wdt_renesas_ra_disable, + .install_timeout = wdt_renesas_ra_install_timeout, + .feed = wdt_renesas_ra_feed, +}; + +#define WDT_RENESAS_RA_DEFINE(id) \ + static struct wdt_renesas_ra_config wdt_renesas_ra_cfg##id = { \ + .clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(id)), \ + .clock_subsys = {.mstp = DT_CLOCKS_CELL(id, mstp), \ + .stop_bit = DT_CLOCKS_CELL(id, stop_bit)}, \ + }; \ + \ + static struct wdt_renesas_ra_data wdt_renesas_ra_data##id = { \ + .device_state = ATOMIC_INIT(0), \ + .wdt_cfg = {.stop_control = WDT_STOP_CONTROL_DISABLE, \ + .reset_control = WDT_RESET_CONTROL_RESET, \ + .p_callback = wdt_renesas_ra_callback_adapter, \ + .p_context = DEVICE_DT_GET(id)}, \ + }; \ + \ + DEVICE_DT_DEFINE(id, NULL, NULL, &wdt_renesas_ra_data##id, &wdt_renesas_ra_cfg##id, \ + POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_renesas_ra_api); + +DT_FOREACH_STATUS_OKAY(renesas_ra_wdt, WDT_RENESAS_RA_DEFINE) diff --git a/dts/bindings/watchdog/renesas,ra-wdt.yaml b/dts/bindings/watchdog/renesas,ra-wdt.yaml new file mode 100644 index 00000000000..f50b9e4f4b2 --- /dev/null +++ b/dts/bindings/watchdog/renesas,ra-wdt.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2025 Renesas Electronics Corporation +# SPDX-License-Identifier: Apache-2.0 + +description: Renesas RA Watchdog (wdt) + +compatible: "renesas,ra-wdt" + +include: base.yaml + +properties: + reg: + required: true + + clocks: + required: true diff --git a/modules/Kconfig.renesas_fsp b/modules/Kconfig.renesas_fsp index 5b2f80ebe84..326deb29b1b 100644 --- a/modules/Kconfig.renesas_fsp +++ b/modules/Kconfig.renesas_fsp @@ -159,6 +159,11 @@ config USE_RA_FSP_ACMPHS help Enable RA FSP ACMPHS driver +config USE_RA_FSP_WDT + bool + help + Enable RA FSP WDT driver + endif # HAS_RENESAS_RA_FSP if HAS_RENESAS_RZ_FSP