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