1 /*
2 * Copyright (c) 2019 Interay Solutions B.V.
3 * Copyright (c) 2019 Oane Kingma
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 #define DT_DRV_COMPAT silabs_gecko_wdog
9
10 #include <soc.h>
11 #include <zephyr/drivers/watchdog.h>
12 #include <zephyr/irq.h>
13 #include <em_wdog.h>
14 #include <em_cmu.h>
15
16 #include <zephyr/logging/log.h>
17 #include <zephyr/irq.h>
18 LOG_MODULE_REGISTER(wdt_gecko, CONFIG_WDT_LOG_LEVEL);
19
20 #ifdef cmuClock_CORELE
21 #define CLOCK_DEF(id) cmuClock_CORELE
22 #else
23 #define CLOCK_DEF(id) cmuClock_WDOG##id
24 #endif /* cmuClock_CORELE */
25 #define CLOCK_ID(id) CLOCK_DEF(id)
26
27 /* Defines maximum WDOG_CTRL.PERSEL value which is used by the watchdog module
28 * to select its timeout period.
29 */
30 #define WDT_GECKO_MAX_PERIOD_SELECT_VALUE 15
31
32 /* Device constant configuration parameters */
33 struct wdt_gecko_cfg {
34 WDOG_TypeDef *base;
35 CMU_Clock_TypeDef clock;
36 void (*irq_cfg_func)(void);
37 };
38
39 struct wdt_gecko_data {
40 wdt_callback_t callback;
41 WDOG_Init_TypeDef wdog_config;
42 bool timeout_installed;
43 };
44
wdt_gecko_get_timeout_from_persel(int perSel)45 static uint32_t wdt_gecko_get_timeout_from_persel(int perSel)
46 {
47 return (8 << perSel) + 1;
48 }
49
50 /* Find the rounded up value of cycles for supplied timeout. When using ULFRCO
51 * (default), 1 cycle is 1 ms +/- 12%.
52 */
wdt_gecko_get_persel_from_timeout(uint32_t timeout)53 static int wdt_gecko_get_persel_from_timeout(uint32_t timeout)
54 {
55 int idx;
56
57 for (idx = 0; idx < WDT_GECKO_MAX_PERIOD_SELECT_VALUE; idx++) {
58 if (wdt_gecko_get_timeout_from_persel(idx) >= timeout) {
59 break;
60 }
61 }
62
63 return idx;
64 }
65
wdt_gecko_convert_window(uint32_t window,uint32_t period)66 static int wdt_gecko_convert_window(uint32_t window, uint32_t period)
67 {
68 int idx = 0;
69 uint32_t incr_val, comp_val;
70
71 incr_val = period / 8;
72 comp_val = 0; /* Initially 0, disable */
73
74 /* Valid window settings range from 12.5% of the calculated
75 * timeout period up to 87.5% (= 7 * 12.5%)
76 */
77 while (idx < 7) {
78 if (window > comp_val) {
79 comp_val += incr_val;
80 idx++;
81 continue;
82 }
83
84 break;
85 }
86
87 return idx;
88 }
89
wdt_gecko_setup(const struct device * dev,uint8_t options)90 static int wdt_gecko_setup(const struct device *dev, uint8_t options)
91 {
92 const struct wdt_gecko_cfg *config = dev->config;
93 struct wdt_gecko_data *data = dev->data;
94 WDOG_TypeDef *wdog = config->base;
95
96 if (!data->timeout_installed) {
97 LOG_ERR("No valid timeouts installed");
98 return -EINVAL;
99 }
100
101 #if defined(_WDOG_CFG_EM1RUN_MASK)
102 data->wdog_config.em1Run =
103 (options & WDT_OPT_PAUSE_IN_SLEEP) == 0U;
104 #endif
105 data->wdog_config.em2Run =
106 (options & WDT_OPT_PAUSE_IN_SLEEP) == 0U;
107 data->wdog_config.em3Run =
108 (options & WDT_OPT_PAUSE_IN_SLEEP) == 0U;
109
110 data->wdog_config.debugRun =
111 (options & WDT_OPT_PAUSE_HALTED_BY_DBG) == 0U;
112
113 if (data->callback != NULL) {
114 /* Interrupt mode for window */
115 /* Clear possible lingering interrupts */
116 WDOGn_IntClear(wdog, WDOG_IEN_TOUT);
117 /* Enable timeout interrupt */
118 WDOGn_IntEnable(wdog, WDOG_IEN_TOUT);
119 } else {
120 /* Disable timeout interrupt */
121 WDOGn_IntDisable(wdog, WDOG_IEN_TOUT);
122 }
123
124 /* Watchdog is started after initialization */
125 WDOGn_Init(wdog, &data->wdog_config);
126 LOG_DBG("Setup the watchdog");
127
128 return 0;
129 }
130
wdt_gecko_disable(const struct device * dev)131 static int wdt_gecko_disable(const struct device *dev)
132 {
133 const struct wdt_gecko_cfg *config = dev->config;
134 struct wdt_gecko_data *data = dev->data;
135 WDOG_TypeDef *wdog = config->base;
136
137 WDOGn_Enable(wdog, false);
138 data->timeout_installed = false;
139 LOG_DBG("Disabled the watchdog");
140
141 return 0;
142 }
143
wdt_gecko_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)144 static int wdt_gecko_install_timeout(const struct device *dev,
145 const struct wdt_timeout_cfg *cfg)
146 {
147 struct wdt_gecko_data *data = dev->data;
148 data->wdog_config = (WDOG_Init_TypeDef)WDOG_INIT_DEFAULT;
149 uint32_t installed_timeout;
150
151 if (data->timeout_installed) {
152 LOG_ERR("No more timeouts can be installed");
153 return -ENOMEM;
154 }
155
156 if ((cfg->window.max < wdt_gecko_get_timeout_from_persel(0)) ||
157 (cfg->window.max > wdt_gecko_get_timeout_from_persel(
158 WDT_GECKO_MAX_PERIOD_SELECT_VALUE))) {
159 LOG_ERR("Upper limit timeout out of range");
160 return -EINVAL;
161 }
162
163 #if defined(_WDOG_CTRL_CLKSEL_MASK)
164 data->wdog_config.clkSel = wdogClkSelULFRCO;
165 #endif
166
167 data->wdog_config.perSel = (WDOG_PeriodSel_TypeDef)
168 wdt_gecko_get_persel_from_timeout(cfg->window.max);
169
170 installed_timeout = wdt_gecko_get_timeout_from_persel(
171 data->wdog_config.perSel);
172 LOG_INF("Installed timeout value: %u", installed_timeout);
173
174 if (cfg->window.min > 0) {
175 /* Window mode. Use rounded up timeout value to
176 * calculate minimum window setting.
177 */
178 data->wdog_config.winSel = (WDOG_WinSel_TypeDef)
179 wdt_gecko_convert_window(cfg->window.min,
180 installed_timeout);
181
182 LOG_INF("Installed window value: %u",
183 (installed_timeout / 8) * data->wdog_config.winSel);
184 } else {
185 /* Normal mode */
186 data->wdog_config.winSel = wdogIllegalWindowDisable;
187 }
188
189 /* Set mode of watchdog and callback */
190 switch (cfg->flags) {
191 case WDT_FLAG_RESET_SOC:
192 case WDT_FLAG_RESET_CPU_CORE:
193 if (cfg->callback != NULL) {
194 LOG_ERR("Reset mode with callback not supported\n");
195 return -ENOTSUP;
196 }
197 data->wdog_config.resetDisable = false;
198 LOG_DBG("Configuring reset CPU/SoC mode\n");
199 break;
200
201 case WDT_FLAG_RESET_NONE:
202 data->wdog_config.resetDisable = true;
203 data->callback = cfg->callback;
204 LOG_DBG("Configuring non-reset mode\n");
205 break;
206
207 default:
208 LOG_ERR("Unsupported watchdog config flag");
209 return -EINVAL;
210 }
211
212 data->timeout_installed = true;
213
214 return 0;
215 }
216
wdt_gecko_feed(const struct device * dev,int channel_id)217 static int wdt_gecko_feed(const struct device *dev, int channel_id)
218 {
219 const struct wdt_gecko_cfg *config = dev->config;
220 WDOG_TypeDef *wdog = config->base;
221
222 if (channel_id != 0) {
223 LOG_ERR("Invalid channel id");
224 return -EINVAL;
225 }
226
227 WDOGn_Feed(wdog);
228 LOG_DBG("Fed the watchdog");
229
230 return 0;
231 }
232
wdt_gecko_isr(const struct device * dev)233 static void wdt_gecko_isr(const struct device *dev)
234 {
235 const struct wdt_gecko_cfg *config = dev->config;
236 struct wdt_gecko_data *data = dev->data;
237 WDOG_TypeDef *wdog = config->base;
238 uint32_t flags;
239
240 /* Clear IRQ flags */
241 flags = WDOGn_IntGet(wdog);
242 WDOGn_IntClear(wdog, flags);
243
244 if (data->callback != NULL) {
245 data->callback(dev, 0);
246 }
247 }
248
wdt_gecko_init(const struct device * dev)249 static int wdt_gecko_init(const struct device *dev)
250 {
251 const struct wdt_gecko_cfg *config = dev->config;
252
253 #ifdef CONFIG_WDT_DISABLE_AT_BOOT
254 /* Ignore any errors */
255 wdt_gecko_disable(dev);
256 #endif
257
258 /* Enable ULFRCO (1KHz) oscillator */
259 CMU_OscillatorEnable(cmuOsc_ULFRCO, true, false);
260
261 /* Ensure LE modules are clocked */
262 CMU_ClockEnable(config->clock, true);
263
264 #if defined(_SILICON_LABS_32B_SERIES_2)
265 CMU_ClockSelectSet(config->clock, cmuSelect_ULFRCO);
266 /* Enable Watchdog clock. */
267 CMU_ClockEnable(cmuClock_WDOG0, true);
268 #endif
269
270 /* Enable IRQs */
271 config->irq_cfg_func();
272
273 LOG_INF("Device %s initialized", dev->name);
274
275 return 0;
276 }
277
278 static DEVICE_API(wdt, wdt_gecko_driver_api) = {
279 .setup = wdt_gecko_setup,
280 .disable = wdt_gecko_disable,
281 .install_timeout = wdt_gecko_install_timeout,
282 .feed = wdt_gecko_feed,
283 };
284
285 #define GECKO_WDT_INIT(index) \
286 \
287 static void wdt_gecko_cfg_func_##index(void); \
288 \
289 static const struct wdt_gecko_cfg wdt_gecko_cfg_##index = { \
290 .base = (WDOG_TypeDef *) \
291 DT_INST_REG_ADDR(index),\
292 .clock = CLOCK_ID(DT_INST_PROP(index, peripheral_id)), \
293 .irq_cfg_func = wdt_gecko_cfg_func_##index, \
294 }; \
295 static struct wdt_gecko_data wdt_gecko_data_##index; \
296 \
297 DEVICE_DT_INST_DEFINE(index, \
298 &wdt_gecko_init, NULL, \
299 &wdt_gecko_data_##index, \
300 &wdt_gecko_cfg_##index, POST_KERNEL, \
301 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
302 &wdt_gecko_driver_api); \
303 \
304 static void wdt_gecko_cfg_func_##index(void) \
305 { \
306 IRQ_CONNECT(DT_INST_IRQN(index), \
307 DT_INST_IRQ(index, priority),\
308 wdt_gecko_isr, DEVICE_DT_INST_GET(index), 0); \
309 irq_enable(DT_INST_IRQN(index)); \
310 }
311
312 DT_INST_FOREACH_STATUS_OKAY(GECKO_WDT_INIT)
313