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 data->wdog_config.em2Run =
102 (options & WDT_OPT_PAUSE_IN_SLEEP) == 0U;
103 data->wdog_config.em3Run =
104 (options & WDT_OPT_PAUSE_IN_SLEEP) == 0U;
105
106 data->wdog_config.debugRun =
107 (options & WDT_OPT_PAUSE_HALTED_BY_DBG) == 0U;
108
109 if (data->callback != NULL) {
110 /* Interrupt mode for window */
111 /* Clear possible lingering interrupts */
112 WDOGn_IntClear(wdog, WDOG_IEN_TOUT);
113 /* Enable timeout interrupt */
114 WDOGn_IntEnable(wdog, WDOG_IEN_TOUT);
115 } else {
116 /* Disable timeout interrupt */
117 WDOGn_IntDisable(wdog, WDOG_IEN_TOUT);
118 }
119
120 /* Watchdog is started after initialization */
121 WDOGn_Init(wdog, &data->wdog_config);
122 LOG_DBG("Setup the watchdog");
123
124 return 0;
125 }
126
wdt_gecko_disable(const struct device * dev)127 static int wdt_gecko_disable(const struct device *dev)
128 {
129 const struct wdt_gecko_cfg *config = dev->config;
130 struct wdt_gecko_data *data = dev->data;
131 WDOG_TypeDef *wdog = config->base;
132
133 WDOGn_Enable(wdog, false);
134 data->timeout_installed = false;
135 LOG_DBG("Disabled the watchdog");
136
137 return 0;
138 }
139
wdt_gecko_install_timeout(const struct device * dev,const struct wdt_timeout_cfg * cfg)140 static int wdt_gecko_install_timeout(const struct device *dev,
141 const struct wdt_timeout_cfg *cfg)
142 {
143 struct wdt_gecko_data *data = dev->data;
144 data->wdog_config = (WDOG_Init_TypeDef)WDOG_INIT_DEFAULT;
145 uint32_t installed_timeout;
146
147 if (data->timeout_installed) {
148 LOG_ERR("No more timeouts can be installed");
149 return -ENOMEM;
150 }
151
152 if ((cfg->window.max < wdt_gecko_get_timeout_from_persel(0)) ||
153 (cfg->window.max > wdt_gecko_get_timeout_from_persel(
154 WDT_GECKO_MAX_PERIOD_SELECT_VALUE))) {
155 LOG_ERR("Upper limit timeout out of range");
156 return -EINVAL;
157 }
158
159 #if defined(_WDOG_CTRL_CLKSEL_MASK)
160 data->wdog_config.clkSel = wdogClkSelULFRCO;
161 #endif
162
163 data->wdog_config.perSel = (WDOG_PeriodSel_TypeDef)
164 wdt_gecko_get_persel_from_timeout(cfg->window.max);
165
166 installed_timeout = wdt_gecko_get_timeout_from_persel(
167 data->wdog_config.perSel);
168 LOG_INF("Installed timeout value: %u", installed_timeout);
169
170 if (cfg->window.min > 0) {
171 /* Window mode. Use rounded up timeout value to
172 * calculate minimum window setting.
173 */
174 data->wdog_config.winSel = (WDOG_WinSel_TypeDef)
175 wdt_gecko_convert_window(cfg->window.min,
176 installed_timeout);
177
178 LOG_INF("Installed window value: %u",
179 (installed_timeout / 8) * data->wdog_config.winSel);
180 } else {
181 /* Normal mode */
182 data->wdog_config.winSel = wdogIllegalWindowDisable;
183 }
184
185 /* Set mode of watchdog and callback */
186 switch (cfg->flags) {
187 case WDT_FLAG_RESET_SOC:
188 case WDT_FLAG_RESET_CPU_CORE:
189 if (cfg->callback != NULL) {
190 LOG_ERR("Reset mode with callback not supported\n");
191 return -ENOTSUP;
192 }
193 data->wdog_config.resetDisable = false;
194 LOG_DBG("Configuring reset CPU/SoC mode\n");
195 break;
196
197 case WDT_FLAG_RESET_NONE:
198 data->wdog_config.resetDisable = true;
199 data->callback = cfg->callback;
200 LOG_DBG("Configuring non-reset mode\n");
201 break;
202
203 default:
204 LOG_ERR("Unsupported watchdog config flag");
205 return -EINVAL;
206 }
207
208 data->timeout_installed = true;
209
210 return 0;
211 }
212
wdt_gecko_feed(const struct device * dev,int channel_id)213 static int wdt_gecko_feed(const struct device *dev, int channel_id)
214 {
215 const struct wdt_gecko_cfg *config = dev->config;
216 WDOG_TypeDef *wdog = config->base;
217
218 if (channel_id != 0) {
219 LOG_ERR("Invalid channel id");
220 return -EINVAL;
221 }
222
223 WDOGn_Feed(wdog);
224 LOG_DBG("Fed the watchdog");
225
226 return 0;
227 }
228
wdt_gecko_isr(const struct device * dev)229 static void wdt_gecko_isr(const struct device *dev)
230 {
231 const struct wdt_gecko_cfg *config = dev->config;
232 struct wdt_gecko_data *data = dev->data;
233 WDOG_TypeDef *wdog = config->base;
234 uint32_t flags;
235
236 /* Clear IRQ flags */
237 flags = WDOGn_IntGet(wdog);
238 WDOGn_IntClear(wdog, flags);
239
240 if (data->callback != NULL) {
241 data->callback(dev, 0);
242 }
243 }
244
wdt_gecko_init(const struct device * dev)245 static int wdt_gecko_init(const struct device *dev)
246 {
247 const struct wdt_gecko_cfg *config = dev->config;
248
249 #ifdef CONFIG_WDT_DISABLE_AT_BOOT
250 /* Ignore any errors */
251 wdt_gecko_disable(dev);
252 #endif
253
254 /* Enable ULFRCO (1KHz) oscillator */
255 CMU_OscillatorEnable(cmuOsc_ULFRCO, true, false);
256
257 /* Ensure LE modules are clocked */
258 CMU_ClockEnable(config->clock, true);
259
260 #if defined(_SILICON_LABS_32B_SERIES_2)
261 CMU_ClockSelectSet(config->clock, cmuSelect_ULFRCO);
262 /* Enable Watchdog clock. */
263 CMU_ClockEnable(cmuClock_WDOG0, true);
264 #endif
265
266 /* Enable IRQs */
267 config->irq_cfg_func();
268
269 LOG_INF("Device %s initialized", dev->name);
270
271 return 0;
272 }
273
274 static const struct wdt_driver_api wdt_gecko_driver_api = {
275 .setup = wdt_gecko_setup,
276 .disable = wdt_gecko_disable,
277 .install_timeout = wdt_gecko_install_timeout,
278 .feed = wdt_gecko_feed,
279 };
280
281 #define GECKO_WDT_INIT(index) \
282 \
283 static void wdt_gecko_cfg_func_##index(void); \
284 \
285 static const struct wdt_gecko_cfg wdt_gecko_cfg_##index = { \
286 .base = (WDOG_TypeDef *) \
287 DT_INST_REG_ADDR(index),\
288 .clock = CLOCK_ID(DT_INST_PROP(index, peripheral_id)), \
289 .irq_cfg_func = wdt_gecko_cfg_func_##index, \
290 }; \
291 static struct wdt_gecko_data wdt_gecko_data_##index; \
292 \
293 DEVICE_DT_INST_DEFINE(index, \
294 &wdt_gecko_init, NULL, \
295 &wdt_gecko_data_##index, \
296 &wdt_gecko_cfg_##index, POST_KERNEL, \
297 CONFIG_KERNEL_INIT_PRIORITY_DEFAULT, \
298 &wdt_gecko_driver_api); \
299 \
300 static void wdt_gecko_cfg_func_##index(void) \
301 { \
302 IRQ_CONNECT(DT_INST_IRQN(index), \
303 DT_INST_IRQ(index, priority),\
304 wdt_gecko_isr, DEVICE_DT_INST_GET(index), 0); \
305 irq_enable(DT_INST_IRQN(index)); \
306 }
307
308 DT_INST_FOREACH_STATUS_OKAY(GECKO_WDT_INIT)
309