1 /*
2  * Copyright (c) 2025 Renesas Electronics Corporation
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <soc.h>
8 #include <zephyr/kernel.h>
9 #include <zephyr/drivers/clock_control/renesas_ra_cgc.h>
10 #include <zephyr/logging/log.h>
11 #include <zephyr/drivers/watchdog.h>
12 #include "r_wdt.h"
13 
14 LOG_MODULE_REGISTER(wdt_renesas_ra, CONFIG_WDT_LOG_LEVEL);
15 
16 struct wdt_renesas_ra_config {
17 	const struct device *clock_dev;
18 	const struct clock_control_ra_subsys_cfg clock_subsys;
19 };
20 
21 struct wdt_renesas_ra_data {
22 	struct wdt_timeout_cfg timeout;
23 	struct st_wdt_instance_ctrl wdt_ctrl;
24 	struct st_wdt_cfg wdt_cfg;
25 	struct k_mutex inst_lock;
26 	atomic_t device_state;
27 };
28 
29 #define WDT_RENESAS_RA_ATOMIC_ENABLE      (0)
30 #define WDT_RENESAS_RA_ATOMIC_TIMEOUT_SET (1)
31 
32 /* Lookup table for WDT period raw cycle */
33 const float timeout_period_lut[] = {
34 	[WDT_TIMEOUT_128] = 128,    [WDT_TIMEOUT_512] = 512,   [WDT_TIMEOUT_1024] = 1024,
35 	[WDT_TIMEOUT_2048] = 2048,  [WDT_TIMEOUT_4096] = 4096, [WDT_TIMEOUT_8192] = 8192,
36 	[WDT_TIMEOUT_16384] = 16384};
37 
38 /* Lookup table for the division value of the input clock count */
39 const float clock_div_lut[] = {[WDT_CLOCK_DIVISION_1] = 1,       [WDT_CLOCK_DIVISION_4] = 4,
40 			       [WDT_CLOCK_DIVISION_16] = 16,     [WDT_CLOCK_DIVISION_32] = 32,
41 			       [WDT_CLOCK_DIVISION_64] = 64,     [WDT_CLOCK_DIVISION_128] = 128,
42 			       [WDT_CLOCK_DIVISION_256] = 256,   [WDT_CLOCK_DIVISION_512] = 512,
43 			       [WDT_CLOCK_DIVISION_2048] = 2048, [WDT_CLOCK_DIVISION_8192] = 8192};
44 
45 #define WDT_WINDOW_INVALID (-1)
46 
47 /* Lookup table for the window start position setting */
48 const int window_start_lut[] = {
49 	[0] = WDT_WINDOW_START_100, [1] = WDT_WINDOW_START_75, [2] = WDT_WINDOW_START_50,
50 	[3] = WDT_WINDOW_START_25,  [4] = WDT_WINDOW_INVALID,
51 };
52 
53 /* Lookup table for the window end position setting */
54 const int window_end_lut[] = {
55 	[0] = WDT_WINDOW_INVALID, [1] = WDT_WINDOW_END_75, [2] = WDT_WINDOW_END_50,
56 	[3] = WDT_WINDOW_END_25,  [4] = WDT_WINDOW_END_0,
57 };
58 
wdt_renesas_ra_inst_lock(const struct device * dev)59 static inline void wdt_renesas_ra_inst_lock(const struct device *dev)
60 {
61 	struct wdt_renesas_ra_data *data = dev->data;
62 
63 	k_mutex_lock(&data->inst_lock, K_FOREVER);
64 }
65 
wdt_renesas_ra_inst_unlock(const struct device * dev)66 static inline void wdt_renesas_ra_inst_unlock(const struct device *dev)
67 {
68 	struct wdt_renesas_ra_data *data = dev->data;
69 
70 	k_mutex_unlock(&data->inst_lock);
71 }
72 
wdt_renesas_ra_timeout_calculate(const struct device * dev,const struct wdt_timeout_cfg * config)73 static int wdt_renesas_ra_timeout_calculate(const struct device *dev,
74 					    const struct wdt_timeout_cfg *config)
75 {
76 	struct wdt_renesas_ra_data *data = dev->data;
77 	const struct wdt_renesas_ra_config *cfg = dev->config;
78 	struct st_wdt_cfg *p_cfg = &data->wdt_cfg;
79 	unsigned int window_start_idx;
80 	unsigned int window_end_idx;
81 	unsigned int best_divisor = WDT_CLOCK_DIVISION_1;
82 	unsigned int best_timeout = WDT_TIMEOUT_128;
83 	unsigned int best_period_ms = UINT_MAX;
84 	unsigned int min_delta = UINT_MAX;
85 	uint32_t clock_rate;
86 	int ret;
87 
88 	if (atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_TIMEOUT_SET)) {
89 		if (config->window.min != data->timeout.window.min ||
90 		    config->window.max != data->timeout.window.max ||
91 		    config->flags != data->timeout.flags) {
92 			LOG_ERR("wdt support only one timeout setting value");
93 			return -EINVAL;
94 		}
95 
96 		data->timeout.callback = config->callback;
97 		return 0;
98 	}
99 
100 	ret = clock_control_get_rate(cfg->clock_dev, (clock_control_subsys_t)&cfg->clock_subsys,
101 				     &clock_rate);
102 	if (unlikely(ret)) {
103 		return ret;
104 	}
105 
106 	for (unsigned int divisor = WDT_CLOCK_DIVISION_1; divisor < ARRAY_SIZE(clock_div_lut);
107 	     divisor++) {
108 		for (unsigned int timeout = WDT_TIMEOUT_128;
109 		     timeout < ARRAY_SIZE(timeout_period_lut); timeout++) {
110 			unsigned int period_ms =
111 				(unsigned int)(1000.0F * clock_div_lut[divisor] *
112 					       timeout_period_lut[timeout] / (float)clock_rate);
113 			unsigned int delta = period_ms > config->window.max
114 						     ? period_ms - config->window.max
115 						     : config->window.max - period_ms;
116 
117 			if (delta < min_delta) {
118 				min_delta = delta;
119 				best_divisor = divisor;
120 				best_timeout = timeout;
121 				best_period_ms = period_ms;
122 			}
123 		}
124 	}
125 
126 	if (min_delta == UINT_MAX) {
127 		LOG_ERR("wdt timeout out of range");
128 		return -EINVAL;
129 	}
130 
131 	window_start_idx = (config->window.min * 4 + best_period_ms - 1) / best_period_ms;
132 	window_end_idx = (config->window.max * 4 + best_period_ms - 1) / best_period_ms;
133 
134 	if (window_start_lut[window_start_idx] == WDT_WINDOW_INVALID ||
135 	    window_end_lut[window_end_idx] == WDT_WINDOW_INVALID) {
136 		LOG_ERR("this wdt timeout is not supported");
137 		return -ENOTSUP;
138 	}
139 
140 	p_cfg->clock_division = (wdt_clock_division_t)best_divisor;
141 	p_cfg->timeout = (wdt_timeout_t)best_timeout;
142 	p_cfg->window_start = (wdt_window_start_t)window_start_lut[window_start_idx];
143 	p_cfg->window_end = (wdt_window_end_t)window_end_lut[window_end_idx];
144 
145 	LOG_INF("actual window min = %.2f ms", window_start_idx * best_period_ms * 0.25);
146 	LOG_INF("actual window max = %.2f ms", window_end_idx * best_period_ms * 0.25);
147 
148 	memcpy(&data->timeout, config, sizeof(struct wdt_timeout_cfg));
149 
150 	return 0;
151 }
152 
wdt_renesas_ra_setup(const struct device * dev,uint8_t options)153 static int wdt_renesas_ra_setup(const struct device *dev, uint8_t options)
154 {
155 	struct wdt_renesas_ra_data *data = dev->data;
156 	int ret = 0;
157 
158 	/*
159 	 * TODO: WDT must be restarted with wdt_feed call after sleep -> will be added later when PM
160 	 * mode is supported
161 	 */
162 	if ((options & WDT_OPT_PAUSE_IN_SLEEP) != 0) {
163 		LOG_ERR("wdt pause in sleep mode not supported");
164 		return -ENOTSUP;
165 	}
166 
167 	wdt_renesas_ra_inst_lock(dev);
168 
169 	if (atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE)) {
170 		LOG_ERR("wdt has been already setup");
171 		ret = -EBUSY;
172 		goto end;
173 	}
174 
175 	if (!atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_TIMEOUT_SET)) {
176 		LOG_ERR("wdt timeout should be installed before");
177 		ret = -EFAULT;
178 		goto end;
179 	}
180 
181 	/* Pause watchdog timer when CPU is halted by the debugger. */
182 	R_DEBUG->DBGSTOPCR_b.DBGSTOP_WDT = (options & WDT_OPT_PAUSE_HALTED_BY_DBG) != 0 ? 1 : 0;
183 
184 	if (R_WDT_Open(&data->wdt_ctrl, &data->wdt_cfg) != FSP_SUCCESS) {
185 		LOG_ERR("wdt setup failed");
186 		ret = -EIO;
187 		goto end;
188 	}
189 
190 	if (R_WDT_Refresh(&data->wdt_ctrl) != FSP_SUCCESS) {
191 		LOG_ERR("wdt start failed");
192 		ret = -EIO;
193 		goto end;
194 	}
195 
196 	atomic_set_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE);
197 
198 end:
199 	wdt_renesas_ra_inst_unlock(dev);
200 
201 	return ret;
202 }
203 
wdt_renesas_ra_disable(const struct device * dev)204 static int wdt_renesas_ra_disable(const struct device *dev)
205 {
206 	struct wdt_renesas_ra_data *data = dev->data;
207 
208 	if (!atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE)) {
209 		LOG_ERR("wdt has not been enabled yet");
210 		return -EFAULT;
211 	}
212 
213 	LOG_ERR("wdt can not be stopped once it has started");
214 	return -EPERM;
215 }
216 
217 #ifdef CONFIG_WDT_RENESAS_RA_NMI
wdt_renesas_ra_callback_adapter(wdt_callback_args_t * p_args)218 void wdt_renesas_ra_callback_adapter(wdt_callback_args_t *p_args)
219 {
220 	const struct device *dev = p_args->p_context;
221 	struct wdt_renesas_ra_data *data = dev->data;
222 	wdt_callback_t callback = data->timeout.callback;
223 
224 	if (callback != NULL) {
225 		callback(dev, 0);
226 	}
227 }
228 #endif /* CONFIG_WDT_RENESAS_RA_NMI */
229 
230 #define WDT_RENESAS_RA_SUPPORTED_FLAGS (WDT_FLAG_RESET_NONE | WDT_FLAG_RESET_SOC)
231 
wdt_renesas_ra_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * config)232 static int wdt_renesas_ra_install_timeout(const struct device *dev,
233 					  const struct wdt_timeout_cfg *config)
234 {
235 	struct wdt_renesas_ra_data *data = dev->data;
236 	struct st_wdt_cfg *p_config = &data->wdt_cfg;
237 	int ret = 0;
238 
239 	if (config->window.min > config->window.max || config->window.max == 0) {
240 		return -EINVAL;
241 	}
242 
243 	if ((config->flags & ~WDT_RENESAS_RA_SUPPORTED_FLAGS) != 0) {
244 		return -ENOTSUP;
245 	}
246 
247 	if (config->callback == NULL && (config->flags & WDT_FLAG_RESET_MASK) == 0) {
248 		LOG_ERR("no timeout response was chosen");
249 		return -EINVAL;
250 	}
251 
252 	if (config->callback != NULL && (config->flags & WDT_FLAG_RESET_MASK) != 0) {
253 		LOG_ERR("WDT_FLAG_RESET_NONE should be chosen in case of interrupt response");
254 		return -ENOTSUP;
255 	}
256 
257 	wdt_renesas_ra_inst_lock(dev);
258 
259 	if (atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE)) {
260 		LOG_ERR("cannot change timeout settings after wdt setup");
261 		ret = -EBUSY;
262 		goto end;
263 	}
264 
265 #ifndef CONFIG_WDT_RENESAS_RA_NMI
266 	if (config->callback != NULL) {
267 		LOG_ERR("interrupt response only available in case CONFIG_WDT_RENESAS_RA_NMI=y");
268 		ret = -ENOTSUP;
269 		goto end;
270 	}
271 #else
272 	p_config->reset_control = (config->flags & WDT_FLAG_RESET_MASK) != 0
273 					  ? WDT_RESET_CONTROL_RESET
274 					  : WDT_RESET_CONTROL_NMI;
275 #endif
276 
277 	ret = wdt_renesas_ra_timeout_calculate(dev, config);
278 	if (ret < 0) {
279 		goto end;
280 	}
281 
282 	atomic_set_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_TIMEOUT_SET);
283 	LOG_INF("wdt timeout was set successfully");
284 
285 end:
286 	wdt_renesas_ra_inst_unlock(dev);
287 
288 	return ret;
289 }
290 
wdt_renesas_ra_feed(const struct device * dev,int channel_id)291 static int wdt_renesas_ra_feed(const struct device *dev, int channel_id)
292 {
293 	struct wdt_renesas_ra_data *data = dev->data;
294 
295 	if (!atomic_test_bit(&data->device_state, WDT_RENESAS_RA_ATOMIC_ENABLE) ||
296 	    channel_id != 0) {
297 		return -EINVAL;
298 	}
299 
300 	if (R_WDT_Refresh(&data->wdt_ctrl) != FSP_SUCCESS) {
301 		return -EIO;
302 	}
303 
304 	return 0;
305 }
306 
307 static DEVICE_API(wdt, wdt_renesas_ra_api) = {
308 	.setup = wdt_renesas_ra_setup,
309 	.disable = wdt_renesas_ra_disable,
310 	.install_timeout = wdt_renesas_ra_install_timeout,
311 	.feed = wdt_renesas_ra_feed,
312 };
313 
314 #define WDT_RENESAS_RA_DEFINE(id)                                                                  \
315 	static struct wdt_renesas_ra_config wdt_renesas_ra_cfg##id = {                             \
316 		.clock_dev = DEVICE_DT_GET(DT_CLOCKS_CTLR(id)),                                    \
317 		.clock_subsys = {.mstp = DT_CLOCKS_CELL(id, mstp),                                 \
318 				 .stop_bit = DT_CLOCKS_CELL(id, stop_bit)},                        \
319 	};                                                                                         \
320                                                                                                    \
321 	static struct wdt_renesas_ra_data wdt_renesas_ra_data##id = {                              \
322 		.device_state = ATOMIC_INIT(0),                                                    \
323 		.wdt_cfg = {.stop_control = WDT_STOP_CONTROL_DISABLE,                              \
324 			    .reset_control = WDT_RESET_CONTROL_RESET,                              \
325 			    .p_callback = wdt_renesas_ra_callback_adapter,                         \
326 			    .p_context = DEVICE_DT_GET(id)},                                       \
327 	};                                                                                         \
328                                                                                                    \
329 	DEVICE_DT_DEFINE(id, NULL, NULL, &wdt_renesas_ra_data##id, &wdt_renesas_ra_cfg##id,        \
330 			 POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEVICE, &wdt_renesas_ra_api);
331 
332 DT_FOREACH_STATUS_OKAY(renesas_ra_wdt, WDT_RENESAS_RA_DEFINE)
333