blob: 8e00bf84abb9036c9b6d93c7d1ee61e07f821f82 [file] [log] [blame]
/*
* Copyright (c) 2025 Renesas Electronics Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#define DT_DRV_COMPAT renesas_rz_wdt
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/drivers/watchdog.h>
#include "r_wdt.h"
#include <math.h>
LOG_MODULE_REGISTER(wdt_renesas_rz, CONFIG_WDT_LOG_LEVEL);
struct wdt_rz_config {
const wdt_api_t *fsp_api;
IRQn_Type irq;
};
struct wdt_rz_data {
struct wdt_timeout_cfg timeout;
wdt_instance_ctrl_t *fsp_ctrl;
wdt_cfg_t *fsp_cfg;
struct k_mutex inst_lock;
uint32_t clock_rate;
atomic_t device_state;
#ifdef CONFIG_WDT_RENESAS_RZ_USE_ICU
struct k_work interrupt_work;
#endif
};
#define WDT_RZ_ATOMIC_ENABLE (0)
#define WDT_RZ_ATOMIC_TIMEOUT_SET (1)
#ifdef CONFIG_WDT_RENESAS_RZ_USE_ICU
void wdt_underflow_isr(void *irq);
#define WDT_RZ_ISR wdt_underflow_isr
#define WDT_RZ_TIMEOUT_UNSUPPORT 0xFF
#define WDT_RZ_CLOCK_DIVISION_UNSUPPORT 0xFF
/* Lookup table for WDT period raw cycle */
static const float timeout_period_lut[] = {[WDT_TIMEOUT_128] = WDT_RZ_TIMEOUT_UNSUPPORT,
[WDT_TIMEOUT_512] = WDT_RZ_TIMEOUT_UNSUPPORT,
[WDT_TIMEOUT_1024] = 1024,
[WDT_TIMEOUT_2048] = WDT_RZ_TIMEOUT_UNSUPPORT,
[WDT_TIMEOUT_4096] = 4096,
[WDT_TIMEOUT_8192] = 8192,
[WDT_TIMEOUT_16384] = 16384};
/* Lookup table for the division value of the input clock count */
static const float clock_div_lut[] = {[WDT_CLOCK_DIVISION_1] = WDT_RZ_CLOCK_DIVISION_UNSUPPORT,
[WDT_CLOCK_DIVISION_4] = 4,
[WDT_CLOCK_DIVISION_16] = WDT_RZ_CLOCK_DIVISION_UNSUPPORT,
[WDT_CLOCK_DIVISION_32] = WDT_RZ_CLOCK_DIVISION_UNSUPPORT,
[WDT_CLOCK_DIVISION_64] = 64,
[WDT_CLOCK_DIVISION_128] = 128,
[WDT_CLOCK_DIVISION_256] = WDT_RZ_CLOCK_DIVISION_UNSUPPORT,
[WDT_CLOCK_DIVISION_512] = 512,
[WDT_CLOCK_DIVISION_2048] = 2048,
[WDT_CLOCK_DIVISION_8192] = 8192};
/* Lookup table for the window start position setting */
static const int window_start_lut[] = {[0] = WDT_WINDOW_START_25,
[1] = WDT_WINDOW_START_50,
[2] = WDT_WINDOW_START_75,
[3] = WDT_WINDOW_START_100};
/* Lookup table for the window end position setting */
static const int window_end_lut[] = {[0] = WDT_WINDOW_END_0,
[1] = WDT_WINDOW_END_25,
[2] = WDT_WINDOW_END_50,
[3] = WDT_WINDOW_END_75};
#else
void wdt_overflow_isr(void *irq);
#define WDT_RZ_ISR wdt_overflow_isr
#define WDT_RZ_PERIOD_MAX 0xFFF
#define WDT_RZ_PERIOD_MIN 0x0
#endif /* CONFIG_WDT_RENESAS_RZ_USE_ICU */
static inline void wdt_rz_inst_lock(const struct device *dev)
{
struct wdt_rz_data *data = dev->data;
k_mutex_lock(&data->inst_lock, K_FOREVER);
}
static inline void wdt_rz_inst_unlock(const struct device *dev)
{
struct wdt_rz_data *data = dev->data;
k_mutex_unlock(&data->inst_lock);
}
static int wdt_rz_timeout_calculate(const struct device *dev, const struct wdt_timeout_cfg *config)
{
struct wdt_rz_data *data = dev->data;
if (atomic_test_bit(&data->device_state, WDT_RZ_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;
}
#ifndef CONFIG_WDT_RENESAS_RZ_USE_ICU
float best_period_cycle;
wdt_extended_cfg_t *wdt_rz_cfg_extend = (wdt_extended_cfg_t *)data->fsp_cfg->p_extend;
float convert_window_to_sec = (float)config->window.max / 1000;
/*
* Calculate period for watchdog's counter.
* For more details, please refer to section 21.3.2 of the RZA3UL User’s Manual: Hardware.
*/
best_period_cycle = ((convert_window_to_sec * data->clock_rate) / (1024 * 1024)) - 1;
if ((best_period_cycle > WDT_RZ_PERIOD_MAX) || (best_period_cycle < WDT_RZ_PERIOD_MIN)) {
LOG_ERR("wdt timeout out of range");
return -EINVAL;
}
wdt_rz_cfg_extend->wdt_timeout = (uint16_t)round(best_period_cycle);
data->fsp_cfg->p_extend = wdt_rz_cfg_extend;
#else
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;
for (unsigned int divisor = WDT_CLOCK_DIVISION_1; divisor < ARRAY_SIZE(clock_div_lut);
divisor++) {
if (clock_div_lut[divisor] == WDT_RZ_CLOCK_DIVISION_UNSUPPORT) {
continue;
}
for (unsigned int timeout = WDT_TIMEOUT_128;
timeout < ARRAY_SIZE(timeout_period_lut); timeout++) {
if (timeout_period_lut[timeout] == WDT_RZ_TIMEOUT_UNSUPPORT) {
continue;
}
unsigned int period_ms = (unsigned int)(1000.0F * clock_div_lut[divisor] *
timeout_period_lut[timeout] /
(float)data->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;
}
if (config->window.max >= best_period_ms) {
window_start_idx = 3;
} else {
window_start_idx =
((config->window.max * 4 + best_period_ms) / best_period_ms) - 1;
}
if (config->window.min > best_period_ms) {
LOG_ERR("window_min invalid");
return -EINVAL;
} else if (config->window.min == 0) {
window_end_idx = 0;
} else {
window_end_idx = ((float)config->window.min / best_period_ms) * 4;
}
data->fsp_cfg->timeout = (wdt_timeout_t)best_timeout;
data->fsp_cfg->clock_division = (wdt_clock_division_t)best_divisor;
data->fsp_cfg->window_start = (wdt_window_start_t)window_start_lut[window_start_idx];
data->fsp_cfg->window_end = (wdt_window_end_t)window_end_lut[window_end_idx];
LOG_INF("actual window min = %d%%", window_end_idx * 25);
LOG_INF("actual window max = %d%%", (window_start_idx + 1) * 25);
#endif /* CONFIG_WDT_RENESAS_RZ_USE_ICU */
data->timeout = *config;
return 0;
}
static int wdt_rz_setup(const struct device *dev, uint8_t options)
{
const struct wdt_rz_config *cfg = dev->config;
struct wdt_rz_data *data = dev->data;
int ret = 0;
if ((options & WDT_OPT_PAUSE_IN_SLEEP) != 0) {
LOG_ERR("wdt pause in sleep mode not supported");
return -ENOTSUP;
}
wdt_rz_inst_lock(dev);
if (atomic_test_bit(&data->device_state, WDT_RZ_ATOMIC_ENABLE)) {
LOG_ERR("wdt has been already setup");
ret = -EBUSY;
goto end;
}
if (!atomic_test_bit(&data->device_state, WDT_RZ_ATOMIC_TIMEOUT_SET)) {
LOG_ERR("wdt timeout should be installed before");
ret = -EFAULT;
goto end;
}
#ifdef CONFIG_WDT_RENESAS_RZ_USE_ICU
/* Clear the error status of the watchdog */
R_ICU->PERIERR_CLR0 |= R_ICU_PERIERR_CLR0_ER_CL7_Msk;
/* Release captured watchdog error status as an PERI_ERR0 event*/
R_ICU->PERIERR_E0MSK0 &= ~R_ICU_PERIERR_E0MSK0_E0_MK7_Msk;
if (R_SYSC_NS->RSTSR0_b.SWR0F) {
/* Clear CPU0 software reset flag */
volatile uint32_t dummy = R_SYSC_NS->RSTSR0;
R_SYSC_NS->RSTSR0_b.SWR0F = 0x00000000U;
dummy = R_SYSC_NS->RSTSR0;
}
#endif
if (cfg->fsp_api->open(data->fsp_ctrl, data->fsp_cfg) != FSP_SUCCESS) {
LOG_ERR("wdt setup failed!");
ret = -EIO;
goto end;
}
if (cfg->fsp_api->refresh(data->fsp_ctrl) != FSP_SUCCESS) {
LOG_ERR("wdt start failed!");
ret = -EIO;
goto end;
}
atomic_set_bit(&data->device_state, WDT_RZ_ATOMIC_ENABLE);
end:
wdt_rz_inst_unlock(dev);
return ret;
}
static int wdt_rz_disable(const struct device *dev)
{
struct wdt_rz_data *data = dev->data;
if (!atomic_test_bit(&data->device_state, WDT_RZ_ATOMIC_ENABLE)) {
LOG_ERR("wdt has not been enabled yet");
return -EFAULT;
}
LOG_ERR("watchdog cannot be stopped once started unless SOC gets a reset");
return -EPERM;
}
static int wdt_rz_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *config)
{
struct wdt_rz_data *data = dev->data;
int ret = 0;
if (config->window.min > config->window.max || config->window.max == 0) {
return -EINVAL;
}
if (config->callback == NULL && (config->flags & WDT_FLAG_RESET_MASK) == 0) {
LOG_ERR("no timeout response was chosen");
return -EINVAL;
}
wdt_rz_inst_lock(dev);
if (atomic_test_bit(&data->device_state, WDT_RZ_ATOMIC_ENABLE)) {
LOG_ERR("cannot change timeout settings after wdt setup");
ret = -EBUSY;
goto end;
}
ret = wdt_rz_timeout_calculate(dev, config);
if (ret < 0) {
goto end;
}
atomic_set_bit(&data->device_state, WDT_RZ_ATOMIC_TIMEOUT_SET);
LOG_INF("wdt timeout was set successfully");
end:
wdt_rz_inst_unlock(dev);
return ret;
}
static int wdt_rz_feed(const struct device *dev, int channel_id)
{
const struct wdt_rz_config *cfg = dev->config;
struct wdt_rz_data *data = dev->data;
if (!atomic_test_bit(&data->device_state, WDT_RZ_ATOMIC_ENABLE)) {
LOG_ERR("WDT has not been enabled yet!");
return -EINVAL;
}
if (channel_id != 0) {
LOG_ERR("Incorrect channel_id!");
return -EINVAL;
}
if (cfg->fsp_api->refresh(data->fsp_ctrl) != FSP_SUCCESS) {
LOG_ERR("Fail to refresh watchdog!");
return -EIO;
}
return 0;
}
void wdt_rz_callback(wdt_callback_args_t *p_args)
{
const struct device *dev = p_args->p_context;
struct wdt_rz_data *data = dev->data;
wdt_callback_t callback = data->timeout.callback;
if (callback != NULL) {
callback(dev, 0);
}
}
static void wdt_rz_isr_adapter(const struct device *dev)
{
const struct wdt_rz_config *cfg = dev->config;
struct wdt_rz_data *data = dev->data;
WDT_RZ_ISR((void *)cfg->irq);
atomic_test_and_clear_bit(&data->device_state, WDT_RZ_ATOMIC_ENABLE);
atomic_test_and_clear_bit(&data->device_state, WDT_RZ_ATOMIC_TIMEOUT_SET);
#ifdef CONFIG_WDT_RENESAS_RZ_USE_ICU
wdt_status_t status = WDT_STATUS_NO_ERROR;
/* Clear watchdog status's interrupt flag */
cfg->fsp_api->statusGet(data->fsp_ctrl, &status);
cfg->fsp_api->statusClear(data->fsp_ctrl, status);
/* Clear the error status of the watchdog */
R_ICU->PERIERR_CLR0 |= R_ICU_PERIERR_CLR0_ER_CL7_Msk;
k_work_submit(&data->interrupt_work);
#endif
}
#ifdef CONFIG_WDT_RENESAS_RZ_USE_ICU
static void wdt_rz_interrupt_work(struct k_work *work)
{
ARG_UNUSED(work);
/* CR52_0 software reset. */
R_BSP_RegisterProtectDisable(BSP_REG_PROTECT_LPC_RESET);
R_SYSC_S->SWRCPU0 = BSP_PRV_RESET_KEY_AUTO_RELEASE;
R_BSP_RegisterProtectEnable(BSP_REG_PROTECT_LPC_RESET);
__asm__ volatile("wfi");
}
#endif
static DEVICE_API(wdt, wdt_rz_api) = {
.setup = wdt_rz_setup,
.disable = wdt_rz_disable,
.install_timeout = wdt_rz_install_timeout,
.feed = wdt_rz_feed,
};
#ifndef CONFIG_WDT_RENESAS_RZ_USE_ICU
static wdt_extended_cfg_t g_wdt_extend_cfg = {
.overflow_ipl = DT_IRQ(DT_NODELABEL(wdt0), priority),
.overflow_irq = DT_IRQ(DT_NODELABEL(wdt0), irq),
};
#define WDT_RZ_CONFIG_EXTEND &g_wdt_extend_cfg
#define WDT_RZ_INIT_WORK_QUEUE
#else
#define WDT_RZ_CONFIG_EXTEND NULL
#define WDT_RZ_INIT_WORK_QUEUE k_work_init(&data->interrupt_work, wdt_rz_interrupt_work)
#endif
#define WDT_RZ_INIT(inst) \
static wdt_cfg_t g_wdt_##inst##_cfg = { \
.timeout = WDT_TIMEOUT_16384, \
.clock_division = WDT_CLOCK_DIVISION_8192, \
.window_start = WDT_WINDOW_START_100, \
.window_end = WDT_WINDOW_END_0, \
.reset_control = WDT_RESET_CONTROL_NMI, \
.stop_control = WDT_STOP_CONTROL_DISABLE, \
.p_callback = wdt_rz_callback, \
.p_context = (void *)DEVICE_DT_INST_GET(inst), \
.p_extend = WDT_RZ_CONFIG_EXTEND, \
}; \
\
static wdt_instance_ctrl_t g_wdt##inst##_ctrl; \
\
static struct wdt_rz_data wdt_rz_data_##inst = { \
.fsp_ctrl = &g_wdt##inst##_ctrl, \
.fsp_cfg = &g_wdt_##inst##_cfg, \
.clock_rate = DT_INST_PROP(inst, clock_freq), \
.device_state = ATOMIC_INIT(0), \
}; \
\
static struct wdt_rz_config wdt_rz_config_##inst = {.fsp_api = &g_wdt_on_wdt, \
.irq = DT_INST_IRQN(inst)}; \
\
static int wdt_rz_init_##inst(const struct device *dev) \
{ \
struct wdt_rz_data *data = dev->data; \
\
k_mutex_init(&data->inst_lock); \
WDT_RZ_INIT_WORK_QUEUE; \
IRQ_CONNECT(DT_INST_IRQN(inst), DT_INST_IRQ(inst, priority), wdt_rz_isr_adapter, \
DEVICE_DT_INST_GET(inst), DT_INST_IRQ(inst, flags)); \
irq_enable(DT_INST_IRQN(inst)); \
\
return 0; \
} \
\
DEVICE_DT_INST_DEFINE(inst, &wdt_rz_init_##inst, NULL, &wdt_rz_data_##inst, \
&wdt_rz_config_##inst, POST_KERNEL, \
CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_rz_api);
DT_INST_FOREACH_STATUS_OKAY(WDT_RZ_INIT)