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