|
| 1 | +/* |
| 2 | + * Copyright (c) 2025 Renesas Electronics Corporation and/or its affiliates |
| 3 | + * |
| 4 | + * SPDX-License-Identifier: Apache-2.0 |
| 5 | + */ |
| 6 | + |
| 7 | +/** |
| 8 | + * @brief Independent Watchdog (IWDT) Driver for Renesas RX |
| 9 | + */ |
| 10 | + |
| 11 | +#define DT_DRV_COMPAT renesas_rx_iwdt |
| 12 | + |
| 13 | +#include <zephyr/drivers/watchdog.h> |
| 14 | +#include <soc.h> |
| 15 | +#include <zephyr/kernel.h> |
| 16 | +#include <stdlib.h> |
| 17 | +#include <zephyr/arch/rx/sw_nmi_table.h> |
| 18 | + |
| 19 | +#include "r_iwdt_rx_if.h" |
| 20 | + |
| 21 | +#include <platform.h> |
| 22 | + |
| 23 | +#define LOG_LEVEL CONFIG_WDT_LOG_LEVEL |
| 24 | +#include <zephyr/logging/log.h> |
| 25 | +LOG_MODULE_REGISTER(wdt_iwdt_rx); |
| 26 | + |
| 27 | +#define IWDT_NODELABEL DT_NODELABEL(iwdt) |
| 28 | +#define IWDT_NMI_VECTOR 2 |
| 29 | +#define IWDT_WINDOW_START DT_PROP(IWDT_NODELABEL, window_start) |
| 30 | +#define IWDT_WINDOW_END DT_PROP(IWDT_NODELABEL, window_end) |
| 31 | + |
| 32 | +#define WDT_RENESAS_RX_SUPPORTED_FLAGS (WDT_FLAG_RESET_NONE | WDT_FLAG_RESET_SOC) |
| 33 | +struct wdt_iwdt_rx_config { |
| 34 | + struct st_iwdt *const regs; |
| 35 | + const struct device *clock_dev; |
| 36 | +}; |
| 37 | + |
| 38 | +struct wdt_iwdt_rx_data { |
| 39 | + wdt_callback_t callback; |
| 40 | + iwdt_config_t iwdt_config; |
| 41 | + bool timeout_installed; |
| 42 | +}; |
| 43 | + |
| 44 | +#if CONFIG_IWDT_RX_NMI |
| 45 | +static void wdt_iwdt_isr(const struct device *dev) |
| 46 | +{ |
| 47 | + struct wdt_iwdt_rx_data *data = dev->data; |
| 48 | + |
| 49 | + if (data->callback) { |
| 50 | + data->callback(dev, 0); |
| 51 | + } |
| 52 | +} |
| 53 | + |
| 54 | +static void iwdt_enable_nmi(void) |
| 55 | +{ |
| 56 | + ICU.NMIER.BIT.IWDTEN = 0x1; |
| 57 | +} |
| 58 | +#endif /* CONFIG_IWDT_RX_NMI */ |
| 59 | + |
| 60 | +static int wdt_iwdt_rx_disable(const struct device *dev) |
| 61 | +{ |
| 62 | + ARG_UNUSED(dev); |
| 63 | + |
| 64 | + /* watchdog cannot be stopped once started */ |
| 65 | + LOG_ERR("Independent Watchdog cannot be stopped once started"); |
| 66 | + |
| 67 | + return -EPERM; |
| 68 | +} |
| 69 | + |
| 70 | +static int wdt_iwdt_rx_feed(const struct device *dev, int channel_id) |
| 71 | +{ |
| 72 | + ARG_UNUSED(dev); |
| 73 | + ARG_UNUSED(channel_id); |
| 74 | + iwdt_err_t err; |
| 75 | + |
| 76 | + /* Reset counter */ |
| 77 | + err = R_IWDT_Control(IWDT_CMD_REFRESH_COUNTING, NULL); |
| 78 | + if (err != IWDT_SUCCESS) { |
| 79 | + return -EIO; |
| 80 | + } |
| 81 | + |
| 82 | + return 0; |
| 83 | +} |
| 84 | + |
| 85 | +static int wdt_iwdt_rx_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg) |
| 86 | +{ |
| 87 | + struct wdt_iwdt_rx_data *data = dev->data; |
| 88 | + const struct wdt_iwdt_rx_config *iwdt_cfg = dev->config; |
| 89 | + uint32_t clock_rate; |
| 90 | + int ret; |
| 91 | + |
| 92 | + /* Get the iwdt clock rate in hz */ |
| 93 | + ret = clock_control_get_rate(iwdt_cfg->clock_dev, NULL, &clock_rate); |
| 94 | + if (ret != 0) { |
| 95 | + return ret; |
| 96 | + } |
| 97 | + |
| 98 | + uint32_t clock_rate_khz = clock_rate / 1000; |
| 99 | + |
| 100 | + const uint16_t timeout_period[4] = {128, 512, 1024, 2048}; |
| 101 | + const uint16_t clock_divide[6][2] = {{IWDT_CLOCK_DIV_1, 1}, {IWDT_CLOCK_DIV_16, 16}, |
| 102 | + {IWDT_CLOCK_DIV_32, 32}, {IWDT_CLOCK_DIV_64, 64}, |
| 103 | + {IWDT_CLOCK_DIV_128, 128}, {IWDT_CLOCK_DIV_256, 256}}; |
| 104 | + int16_t last_error = INT16_MAX; |
| 105 | + int32_t error; |
| 106 | + uint16_t iwdt_tops = 0; |
| 107 | + uint16_t iwdt_clock_div = 0; |
| 108 | + uint16_t iwdt_timeout = ((uint32_t)timeout_period[0] * clock_divide[0][1]) / clock_rate_khz; |
| 109 | + |
| 110 | + if (cfg->window.min > cfg->window.max || cfg->window.max < iwdt_timeout) { |
| 111 | + return -EINVAL; |
| 112 | + } |
| 113 | + |
| 114 | + if ((cfg->flags & ~WDT_RENESAS_RX_SUPPORTED_FLAGS) != 0) { |
| 115 | + return -ENOTSUP; |
| 116 | + } |
| 117 | + |
| 118 | + if (cfg->callback != NULL && (cfg->flags & WDT_FLAG_RESET_MASK) != 0) { |
| 119 | + LOG_ERR("WDT_FLAG_RESET_NONE should be chosen in case of interrupt response"); |
| 120 | + return -ENOTSUP; |
| 121 | + } |
| 122 | + |
| 123 | +#if CONFIG_IWDT_RENESAS_RX_IWDT |
| 124 | + for (int idx_p = 0; idx_p < 4; idx_p++) { |
| 125 | + for (int idx_d = 0; idx_d < 6; idx_d++) { |
| 126 | + iwdt_timeout = ((uint32_t)timeout_period[idx_p] * clock_divide[idx_d][1]) / |
| 127 | + clock_rate_khz; |
| 128 | + error = cfg->window.max - iwdt_timeout; |
| 129 | + |
| 130 | + if (error < 0) { |
| 131 | + break; |
| 132 | + } |
| 133 | + |
| 134 | + if (error < last_error) { |
| 135 | + last_error = error; |
| 136 | + iwdt_tops = idx_p; |
| 137 | + iwdt_clock_div = clock_divide[idx_d][0]; |
| 138 | + } |
| 139 | + } |
| 140 | + } |
| 141 | + |
| 142 | + data->iwdt_config.timeout = iwdt_tops; |
| 143 | + data->iwdt_config.iwdtclk_div = iwdt_clock_div; |
| 144 | + data->iwdt_config.window_start = IWDT_WINDOW_START; |
| 145 | + data->iwdt_config.window_end = IWDT_WINDOW_END; |
| 146 | + data->iwdt_config.timeout_control = |
| 147 | + (cfg->flags & WDT_FLAG_RESET_MASK) != 0 ? IWDT_TIMEOUT_RESET : IWDT_TIMEOUT_NMI; |
| 148 | + data->iwdt_config.count_stop_enable = IWDT_COUNT_STOP_DISABLE; |
| 149 | +#endif /* CONFIG_IWDT_RENESAS_RX_IWDT */ |
| 150 | + |
| 151 | + data->timeout_installed = true; |
| 152 | + |
| 153 | +#if CONFIG_IWDT_RX_NMI |
| 154 | + data->callback = cfg->callback; |
| 155 | +#endif |
| 156 | + |
| 157 | + return 0; |
| 158 | +} |
| 159 | + |
| 160 | +static int wdt_iwdt_rx_setup(const struct device *dev, uint8_t options) |
| 161 | +{ |
| 162 | + struct wdt_iwdt_rx_data *data = dev->data; |
| 163 | + iwdt_err_t err; |
| 164 | + int ret; |
| 165 | + |
| 166 | +#if CONFIG_IWDT_RENESAS_RX_IWDT |
| 167 | + if (!data->timeout_installed) { |
| 168 | + return -EINVAL; |
| 169 | + } |
| 170 | +#endif /* CONFIG_IWDT_RENESAS_RX_IWDT */ |
| 171 | + |
| 172 | + if (options & WDT_OPT_PAUSE_IN_SLEEP) { |
| 173 | + data->iwdt_config.count_stop_enable = IWDT_COUNT_STOP_ENABLE; |
| 174 | + } else { |
| 175 | + data->iwdt_config.count_stop_enable = IWDT_COUNT_STOP_DISABLE; |
| 176 | + } |
| 177 | + |
| 178 | + if (options & WDT_OPT_PAUSE_HALTED_BY_DBG) { |
| 179 | + return -ENOTSUP; |
| 180 | + } |
| 181 | + |
| 182 | +#if CONFIG_IWDT_RX_NMI |
| 183 | + nmi_enable(IWDT_NMI_VECTOR, (nmi_callback_t)wdt_iwdt_isr, (void *)dev); |
| 184 | + iwdt_enable_nmi(); |
| 185 | +#endif /* CONFIG_IWDT_RX_NMI */ |
| 186 | + |
| 187 | +#if CONFIG_IWDT_RENESAS_RX_IWDT |
| 188 | + |
| 189 | + err = R_IWDT_Open(&data->iwdt_config); |
| 190 | + if (err != IWDT_SUCCESS) { |
| 191 | + return -EINVAL; |
| 192 | + } |
| 193 | + |
| 194 | + ret = wdt_iwdt_rx_feed(dev, 0); |
| 195 | + if (ret != 0) { |
| 196 | + return ret; |
| 197 | + } |
| 198 | +#endif /* CONFIG_IWDT_RENESAS_RX_IWDT */ |
| 199 | + |
| 200 | + return 0; |
| 201 | +} |
| 202 | + |
| 203 | +static DEVICE_API(wdt, wdt_iwdt_rx_api) = { |
| 204 | + .disable = wdt_iwdt_rx_disable, |
| 205 | + .feed = wdt_iwdt_rx_feed, |
| 206 | + .install_timeout = wdt_iwdt_rx_install_timeout, |
| 207 | + .setup = wdt_iwdt_rx_setup, |
| 208 | +}; |
| 209 | + |
| 210 | +#define IWDT_RENESAS_RX_DEFINE(idx) \ |
| 211 | + static struct wdt_iwdt_rx_config iwdt_rx_cfg##idx = { \ |
| 212 | + .regs = (struct st_iwdt *)DT_REG_ADDR(IWDT_NODELABEL), \ |
| 213 | + .clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(idx)), \ |
| 214 | + }; \ |
| 215 | + static struct wdt_iwdt_rx_data iwdt_rx_data##idx = { \ |
| 216 | + .timeout_installed = false, \ |
| 217 | + .iwdt_config = {.count_stop_enable = IWDT_COUNT_STOP_DISABLE, \ |
| 218 | + .timeout_control = IWDT_TIMEOUT_RESET}, \ |
| 219 | + }; \ |
| 220 | + \ |
| 221 | + DEVICE_DT_DEFINE(idx, NULL, NULL, &iwdt_rx_data##idx, &iwdt_rx_cfg##idx, POST_KERNEL, \ |
| 222 | + CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_iwdt_rx_api); |
| 223 | + |
| 224 | +DT_FOREACH_STATUS_OKAY(renesas_rx_iwdt, IWDT_RENESAS_RX_DEFINE); |
0 commit comments