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