1 /*
2  * Copyright (c) 2025 Silicon Laboratories Inc.
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <errno.h>
8 #include <zephyr/irq.h>
9 #include <zephyr/types.h>
10 #include <zephyr/device.h>
11 #include <zephyr/sys/util.h>
12 #include <zephyr/sys/bitarray.h>
13 #include <zephyr/drivers/watchdog.h>
14 #include <zephyr/drivers/clock_control.h>
15 #include <math.h>
16 #include "rsi_wwdt.h"
17 #include "rsi_sysrtc.h"
18 
19 #define DT_DRV_COMPAT                       silabs_siwx91x_wdt
20 #define SIWX91X_WDT_SYSTEM_RESET_TIMER_MASK 0x0000001F
21 
22 struct siwx91x_wdt_config {
23 	/* WDT register base address */
24 	MCU_WDT_Type *reg;
25 	/* Pointer to the clock device structure */
26 	const struct device *clock_dev;
27 	/* Clock control subsystem */
28 	clock_control_subsys_t clock_subsys;
29 	/* Function pointer for the IRQ (Interrupt Request) configuration */
30 	void (*irq_config)(void);
31 };
32 
33 struct siwx91x_wdt_data {
34 	/* Callback function to be called on watchdog timer events */
35 	wdt_callback_t callback;
36 	/* WDT operating clock (LF-FSM) frequency */
37 	uint32_t clock_frequency;
38 	/* Timer system reset duration in ms */
39 	uint8_t delay_reset;
40 	/* Timer interrupt duration in ms */
41 	uint8_t delay_irq;
42 	/* Flag indicating the timeout install status */
43 	bool timeout_install_status;
44 	/* Flag indicating the setup status */
45 	bool setup_status;
46 };
47 
48 /* Function to get the delay in milliseconds from the register value */
siwx91x_wdt_delay_from_hw(uint8_t value,int clock_frequency)49 static uint32_t siwx91x_wdt_delay_from_hw(uint8_t value, int clock_frequency)
50 {
51 	uint32_t ticks = BIT(value);
52 	float timeout = (float)ticks / clock_frequency;
53 
54 	timeout *= 1000;
55 	/* Return the timeout value as an unsigned 32-bit integer in milliseconds */
56 	return (uint32_t)timeout;
57 }
58 
59 /* Function to get the register value from the delay in milliseconds */
siwx91x_wdt_delay_to_hw(uint32_t delay,int clock_frequency)60 static uint8_t siwx91x_wdt_delay_to_hw(uint32_t delay, int clock_frequency)
61 {
62 	/* reg_value = log((timeout * clock_frequency)/1000)base2 */
63 	float value = ((float)delay * (float)clock_frequency) / 1000;
64 	float result = log2f(value);
65 
66 	/* Round the result to nearest integer */
67 	result = roundf(result);
68 
69 	return (uint8_t)result;
70 }
71 
siwx91x_wdt_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)72 static int siwx91x_wdt_install_timeout(const struct device *dev, const struct wdt_timeout_cfg *cfg)
73 {
74 	struct siwx91x_wdt_data *data = dev->data;
75 
76 	/* Check the WDT setup status */
77 	if (data->setup_status) {
78 		/* WDT setup is already done */
79 		return -EBUSY;
80 	}
81 
82 	/* Check the WDT timeout  status */
83 	if (data->timeout_install_status) {
84 		/* Only single timeout can be installed */
85 		return -ENOMEM;
86 	}
87 
88 	if (cfg->window.max > siwx91x_wdt_delay_from_hw(SIWX91X_WDT_SYSTEM_RESET_TIMER_MASK,
89 							data->clock_frequency) ||
90 	    cfg->window.max == 0) {
91 		/* Requested value is out of range */
92 		return -EINVAL;
93 	}
94 
95 	if (cfg->window.min > 0) {
96 		/* This feature is currently not supported */
97 		return -ENOTSUP;
98 	}
99 
100 	switch (cfg->flags) {
101 	case WDT_FLAG_RESET_SOC:
102 	case WDT_FLAG_RESET_CPU_CORE:
103 		if (cfg->callback != NULL) {
104 			/* Callback is not supported for reset flags */
105 			return -ENOTSUP;
106 		}
107 		data->delay_reset = siwx91x_wdt_delay_to_hw(cfg->window.max, data->clock_frequency);
108 		/* During a system or CPU core reset, interrupts are not needed. Thus, we set
109 		 * the interrupt time to 0 to ensure no interrupts occur while resetting.
110 		 */
111 		data->delay_irq = 0;
112 		/* Mask the WWDT interrupt */
113 		RSI_WWDT_IntrMask();
114 		break;
115 
116 	case WDT_FLAG_RESET_NONE:
117 		/* Set the reset time to maximum value */
118 		data->delay_reset = SIWX91X_WDT_SYSTEM_RESET_TIMER_MASK;
119 		data->delay_irq = siwx91x_wdt_delay_to_hw(cfg->window.max, data->clock_frequency);
120 		if (cfg->callback != NULL) {
121 			data->callback = cfg->callback;
122 		}
123 		break;
124 
125 	default:
126 		/* Unsupported WDT config options */
127 		return -ENOTSUP;
128 	}
129 
130 	data->timeout_install_status = true;
131 	return 0;
132 }
133 
134 /* Function to setup and start WDT */
siwx91x_wdt_setup(const struct device * dev,uint8_t options)135 static int siwx91x_wdt_setup(const struct device *dev, uint8_t options)
136 {
137 	const struct siwx91x_wdt_config *config = dev->config;
138 	struct siwx91x_wdt_data *data = dev->data;
139 
140 	/* Check the WDT setup status */
141 	if (data->setup_status) {
142 		/* WDT is already running */
143 		return -EBUSY;
144 	}
145 
146 	/* Check the WDT timeout  status */
147 	if (!data->timeout_install_status) {
148 		/* Timeout need to be set before setup */
149 		return -ENOTSUP;
150 	}
151 
152 	if (options & (WDT_OPT_PAUSE_IN_SLEEP)) {
153 		return -ENOTSUP;
154 	}
155 
156 	RSI_WWDT_ConfigSysRstTimer(config->reg, data->delay_reset);
157 	RSI_WWDT_ConfigIntrTimer(config->reg, data->delay_irq);
158 
159 	RSI_WWDT_Start(config->reg);
160 
161 	data->setup_status = true;
162 
163 	return 0;
164 }
165 
siwx91x_wdt_disable(const struct device * dev)166 static int siwx91x_wdt_disable(const struct device *dev)
167 {
168 	const struct siwx91x_wdt_config *config = dev->config;
169 	struct siwx91x_wdt_data *data = dev->data;
170 
171 	if (!data->timeout_install_status) {
172 		/* No timeout installed */
173 		return -EFAULT;
174 	}
175 
176 	RSI_WWDT_Disable(config->reg);
177 
178 	data->timeout_install_status = false;
179 	data->setup_status = false;
180 
181 	return 0;
182 }
183 
siwx91x_wdt_feed(const struct device * dev,int channel_id)184 static int siwx91x_wdt_feed(const struct device *dev, int channel_id)
185 {
186 	const struct siwx91x_wdt_config *config = dev->config;
187 	struct siwx91x_wdt_data *data = dev->data;
188 
189 	if (!(data->timeout_install_status && data->setup_status)) {
190 		/* WDT is not configured */
191 		return -EINVAL;
192 	}
193 
194 	if (channel_id != 0) {
195 		/* Channel id must be 0 */
196 		return -EINVAL;
197 	}
198 
199 	RSI_WWDT_ReStart(config->reg);
200 	return 0;
201 }
202 
siwx91x_wdt_isr(const struct device * dev)203 static void siwx91x_wdt_isr(const struct device *dev)
204 {
205 	const struct siwx91x_wdt_config *config = dev->config;
206 	struct siwx91x_wdt_data *data = dev->data;
207 
208 	/* Clear WDT interrupt */
209 	RSI_WWDT_IntrClear();
210 
211 	if (data->delay_irq) {
212 		/* Restart the timer */
213 		RSI_WWDT_ReStart(config->reg);
214 	}
215 
216 	if (data->callback != NULL) {
217 		data->callback(dev, 0);
218 	}
219 }
220 
siwx91x_wdt_init(const struct device * dev)221 static int siwx91x_wdt_init(const struct device *dev)
222 {
223 	const struct siwx91x_wdt_config *config = dev->config;
224 	struct siwx91x_wdt_data *data = dev->data;
225 	int ret;
226 
227 	ret = clock_control_on(config->clock_dev, config->clock_subsys);
228 	if (ret) {
229 		return ret;
230 	}
231 	ret = clock_control_get_rate(config->clock_dev, config->clock_subsys,
232 				     &data->clock_frequency);
233 	if (ret) {
234 		return ret;
235 	}
236 
237 	RSI_WWDT_Init(config->reg);
238 
239 	config->irq_config();
240 	RSI_WWDT_IntrUnMask();
241 
242 	return 0;
243 }
244 
245 static DEVICE_API(wdt, siwx91x_wdt_driver_api) = {
246 	.setup = siwx91x_wdt_setup,
247 	.disable = siwx91x_wdt_disable,
248 	.install_timeout = siwx91x_wdt_install_timeout,
249 	.feed = siwx91x_wdt_feed,
250 };
251 
252 #define siwx91x_WDT_INIT(inst)                                                                     \
253 	static struct siwx91x_wdt_data siwx91x_wdt_data_##inst;                                    \
254 	static void siwx91x_wdt_irq_configure_##inst(void)                                         \
255 	{                                                                                          \
256 		IRQ_CONNECT(DT_INST_IRQ(inst, irq), DT_INST_IRQ(inst, priority), siwx91x_wdt_isr,  \
257 			    DEVICE_DT_INST_GET(inst), 0);                                          \
258 		irq_enable(DT_INST_IRQ(inst, irq));                                                \
259 	}                                                                                          \
260 	static const struct siwx91x_wdt_config siwx91x_wdt_config_##inst = {                       \
261 		.reg = (MCU_WDT_Type *)DT_INST_REG_ADDR(inst),                                     \
262 		.clock_dev = DEVICE_DT_GET(DT_INST_CLOCKS_CTLR(inst)),                             \
263 		.clock_subsys = (clock_control_subsys_t)DT_INST_PHA(inst, clocks, clkid),          \
264 		.irq_config = siwx91x_wdt_irq_configure_##inst,                                    \
265 	};                                                                                         \
266 	DEVICE_DT_INST_DEFINE(inst, &siwx91x_wdt_init, NULL, &siwx91x_wdt_data_##inst,             \
267 			      &siwx91x_wdt_config_##inst, PRE_KERNEL_1,                            \
268 			      CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, &siwx91x_wdt_driver_api);
269 
270 DT_INST_FOREACH_STATUS_OKAY(siwx91x_WDT_INIT)
271