xref: /optee_os/core/drivers/atmel_wdt.c (revision 3b616eeadd7a95c762adfdfdfd4791c2db2538f2)
1 // SPDX-License-Identifier: BSD-2-Clause
2 /*
3  * Copyright 2022 Microchip
4  */
5 
6 #include <assert.h>
7 #include <drivers/wdt.h>
8 #include <io.h>
9 #include <kernel/delay.h>
10 #include <kernel/dt.h>
11 #include <kernel/dt_driver.h>
12 #include <kernel/interrupt.h>
13 #include <kernel/panic.h>
14 #include <kernel/pm.h>
15 #include <matrix.h>
16 #include <mm/core_mmu.h>
17 #include <tee_api_defines.h>
18 
19 #define WDT_CR			0x0
20 #define WDT_CR_KEY		SHIFT_U32(0xA5, 24)
21 #define WDT_CR_WDRSTT		BIT(0)
22 
23 #define WDT_MR			0x4
24 #define WDT_MR_WDV		GENMASK_32(11, 0)
25 #define WDT_MR_WDV_SET(val)	((val) & WDT_MR_WDV)
26 #define WDT_MR_WDFIEN		BIT(12)
27 #define WDT_MR_WDRSTEN		BIT(13)
28 #define WDT_MR_WDDIS		BIT(15) /* Watchdog Disable of WDT on bit 15 */
29 #define WDT_MR_WDDIS_DWDT	BIT(12) /* Watchdog Disable of DWDT on bit 12 */
30 #define WDT_MR_WDD_SHIFT	16
31 #define WDT_MR_WDD_MASK		GENMASK_32(11, 0)
32 #define WDT_MR_WDD		SHIFT_U32(WDT_MR_WDD_MASK, WDT_MR_WDD_SHIFT)
33 #define WDT_MR_WDD_SET(val) \
34 			SHIFT_U32(((val) & WDT_MR_WDD_MASK), WDT_MR_WDD_SHIFT)
35 #define WDT_MR_WDDBGHLT		BIT(28)
36 #define WDT_MR_WDIDLEHLT	BIT(29)
37 
38 #define WDT_SR			0x8
39 #define WDT_SR_DUNF		BIT(0)
40 #define WDT_SR_DERR		BIT(1)
41 
42 /* DWDT: Watchdog Timer Mode Register */
43 #define WDT_MR_PERIODRST	BIT(4)
44 #define WDT_MR_RPTHRST		BIT(5)
45 
46 /* DWDT: Watchdog Timer Value Register (Read-only) */
47 #define WDT_VR			0x8
48 #define WDT_VR_COUNTER_SHIFT	0
49 #define WDT_VR_COUNTER_MASK	GENMASK_32(11, 0)
50 
51 /* DWDT: Watchdog Timer Window Level Register */
52 #define WDT_WL			0xc
53 #define WDT_WL_RPTH_SHIFT	16
54 #define WDT_WL_RPTH_MASK	GENMASK_32(27, 16)
55 #define WDT_WL_PERIOD_SHIFT	0
56 #define WDT_WL_PERIOD_MASK	GENMASK_32(11, 0)
57 
58 /* DWDT: Watchdog Timer Interrupt Level Register */
59 #define WDT_IL			0x10
60 #define WDT_IL_LVLTH_SHIFT	0
61 #define WDT_IL_LVLTH_MASK	GENMASK_32(11, 0)
62 
63 /* DWDT: Watchdog Timer Interrupt Enable/Disable/Status/Mask Register */
64 #define WDT_IER			0x14
65 #define WDT_IDR			0x18
66 #define WDT_ISR			0x1c
67 #define WDT_IMR			0x20
68 #define WDT_NSRPTHINT		BIT(4)
69 #define WDT_NSPERINT		BIT(3)
70 #define WDT_LVLINT		BIT(2)
71 #define WDT_RPTHINT		BIT(1)
72 #define WDT_PERINT		BIT(0)
73 
74 /*
75  * The watchdog is clocked by a 32768Hz clock/128 and the counter is on
76  * 12 bits.
77  */
78 #define SLOW_CLOCK_FREQ		(32768)
79 #define WDT_CLOCK_FREQ		(SLOW_CLOCK_FREQ / 128)
80 #define WDT_MIN_TIMEOUT		1
81 #define WDT_MAX_TIMEOUT		(BIT(12) / WDT_CLOCK_FREQ)
82 
83 #define WDT_DEFAULT_TIMEOUT	WDT_MAX_TIMEOUT
84 
85 /*
86  * We must wait at least 3 clocks period before accessing registers MR and CR.
87  * Ensure that we see at least 4 edges
88  */
89 #define WDT_REG_ACCESS_UDELAY	(1000000ULL / SLOW_CLOCK_FREQ * 4)
90 
91 #define SEC_TO_WDT(sec)		(((sec) * WDT_CLOCK_FREQ) - 1)
92 
93 #define WDT_ENABLED(mr, dis_mask)	(!((mr) & (dis_mask)))
94 
95 enum wdt_type {
96 	WDT_TYPE_WDT,	/* Watchdog Timer */
97 	WDT_TYPE_DWDT,	/* Dual Watchdog Timer */
98 };
99 
100 struct wdt_compat {
101 	bool wdt_ps; /* Is Peripheral SHDWC Programmable Secure */
102 	enum wdt_type type; /* Type of Watchdog Timer */
103 	uint32_t dis_mask; /* Mask of Watchdog Disable in Mode Register */
104 };
105 
106 struct atmel_wdt {
107 	struct wdt_chip chip;
108 	enum wdt_type type;
109 	uint32_t dis_mask;
110 	vaddr_t base;
111 	uint32_t mr;
112 	bool enabled;
113 };
114 
atmel_wdt_write_sleep(struct atmel_wdt * wdt,uint32_t reg,uint32_t val)115 static void atmel_wdt_write_sleep(struct atmel_wdt *wdt, uint32_t reg,
116 				  uint32_t val)
117 {
118 	udelay(WDT_REG_ACCESS_UDELAY);
119 
120 	io_write32(wdt->base + reg, val);
121 }
122 
atmel_wdt_settimeout(struct wdt_chip * chip,unsigned long timeout)123 static TEE_Result atmel_wdt_settimeout(struct wdt_chip *chip,
124 				       unsigned long timeout)
125 {
126 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
127 
128 	if (wdt->type == WDT_TYPE_WDT) {
129 		wdt->mr &= ~WDT_MR_WDV;
130 		wdt->mr |= WDT_MR_WDV_SET(SEC_TO_WDT(timeout));
131 
132 		/* WDV and WDD only be updated when the watchdog is running */
133 		if (WDT_ENABLED(wdt->mr, wdt->dis_mask))
134 			atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
135 	} else {
136 		io_write32(wdt->base + WDT_WL,
137 			   SHIFT_U32(SEC_TO_WDT(timeout), WDT_WL_PERIOD_SHIFT));
138 	}
139 
140 	return TEE_SUCCESS;
141 }
142 
atmel_wdt_ping(struct wdt_chip * chip)143 static void atmel_wdt_ping(struct wdt_chip *chip)
144 {
145 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
146 
147 	atmel_wdt_write_sleep(wdt, WDT_CR, WDT_CR_KEY | WDT_CR_WDRSTT);
148 }
149 
atmel_wdt_start(struct atmel_wdt * wdt)150 static void atmel_wdt_start(struct atmel_wdt *wdt)
151 {
152 	wdt->mr &= ~wdt->dis_mask;
153 	atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
154 }
155 
atmel_wdt_enable(struct wdt_chip * chip)156 static void atmel_wdt_enable(struct wdt_chip *chip)
157 {
158 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
159 
160 	wdt->enabled = true;
161 	atmel_wdt_start(wdt);
162 }
163 
atmel_wdt_stop(struct atmel_wdt * wdt)164 static void atmel_wdt_stop(struct atmel_wdt *wdt)
165 {
166 	wdt->mr |= wdt->dis_mask;
167 	atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
168 }
169 
atmel_wdt_disable(struct wdt_chip * chip)170 static void atmel_wdt_disable(struct wdt_chip *chip)
171 {
172 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
173 
174 	wdt->enabled = false;
175 	atmel_wdt_stop(wdt);
176 }
177 
atmel_wdt_itr_cb(struct itr_handler * h)178 static enum itr_return atmel_wdt_itr_cb(struct itr_handler *h)
179 {
180 	struct atmel_wdt *wdt = h->data;
181 	uint32_t sr = 0;
182 
183 	if (wdt->type == WDT_TYPE_WDT) {
184 		sr = io_read32(wdt->base + WDT_SR);
185 
186 		if (sr & WDT_SR_DUNF)
187 			DMSG("Watchdog Underflow");
188 		if (sr & WDT_SR_DERR)
189 			DMSG("Watchdog Error");
190 	} else if (wdt->type == WDT_TYPE_DWDT) {
191 		sr = io_read32(wdt->base + WDT_ISR);
192 
193 		if (sr & WDT_NSRPTHINT)
194 			DMSG("NS Watchdog Repeat Threshold Interrupt");
195 		if (sr & WDT_NSPERINT)
196 			DMSG("NS Watchdog Period Interrupt");
197 		if (sr & WDT_LVLINT)
198 			DMSG("Watchdog Level Threshold Interrupt");
199 		if (sr & WDT_RPTHINT)
200 			DMSG("Watchdog Repeat Threshold Interrupt");
201 		if (sr & WDT_PERINT)
202 			DMSG("Watchdog Period Interrupt");
203 	}
204 
205 	panic("Watchdog interrupt");
206 
207 	return ITRR_HANDLED;
208 }
209 
atmel_wdt_init(struct wdt_chip * chip __unused,unsigned long * min_timeout,unsigned long * max_timeout)210 static TEE_Result atmel_wdt_init(struct wdt_chip *chip __unused,
211 				 unsigned long *min_timeout,
212 				 unsigned long *max_timeout)
213 {
214 	*min_timeout = WDT_MIN_TIMEOUT;
215 	*max_timeout = WDT_MAX_TIMEOUT;
216 
217 	return TEE_SUCCESS;
218 }
219 
220 static const struct wdt_ops atmel_wdt_ops = {
221 	.init = atmel_wdt_init,
222 	.start = atmel_wdt_enable,
223 	.stop = atmel_wdt_disable,
224 	.ping = atmel_wdt_ping,
225 	.set_timeout = atmel_wdt_settimeout,
226 };
227 
atmel_wdt_init_hw(struct atmel_wdt * wdt)228 static void atmel_wdt_init_hw(struct atmel_wdt *wdt)
229 {
230 	uint32_t mr = 0;
231 
232 	/*
233 	 * If we are resuming, we disabled the watchdog on suspend but the
234 	 * bootloader might have enabled the watchdog. If so, disable it
235 	 * properly.
236 	 */
237 	if (!WDT_ENABLED(wdt->mr, wdt->dis_mask)) {
238 		mr = io_read32(wdt->base + WDT_MR);
239 		if (WDT_ENABLED(mr, wdt->dis_mask))
240 			io_write32(wdt->base + WDT_MR, mr | wdt->dis_mask);
241 	}
242 
243 	if (wdt->type == WDT_TYPE_WDT) {
244 		/* Enable interrupt, and disable watchdog in debug and idle */
245 		wdt->mr |= WDT_MR_WDFIEN | WDT_MR_WDDBGHLT | WDT_MR_WDIDLEHLT;
246 		/* Enable watchdog reset */
247 		wdt->mr |= WDT_MR_WDRSTEN;
248 		wdt->mr |= WDT_MR_WDD_SET(SEC_TO_WDT(WDT_MAX_TIMEOUT));
249 		wdt->mr |= WDT_MR_WDV_SET(SEC_TO_WDT(WDT_DEFAULT_TIMEOUT));
250 	} else if (wdt->type == WDT_TYPE_DWDT) {
251 		/* Enable interrupt */
252 		io_write32(wdt->base + WDT_ISR, WDT_PERINT);
253 		/* Disable watchdog in debug and idle */
254 		wdt->mr |= WDT_MR_WDDBGHLT | WDT_MR_WDIDLEHLT;
255 		/* Enable watchdog period reset */
256 		wdt->mr |= WDT_MR_PERIODRST;
257 		io_write32(wdt->base + WDT_WL,
258 			   SHIFT_U32(SEC_TO_WDT(WDT_DEFAULT_TIMEOUT),
259 				     WDT_WL_PERIOD_SHIFT));
260 	} else {
261 		panic("Invalid Watchdog");
262 	}
263 
264 	/*
265 	 * If the watchdog was enabled, write the configuration which will ping
266 	 * the watchdog.
267 	 */
268 	if (WDT_ENABLED(wdt->mr, wdt->dis_mask))
269 		io_write32(wdt->base + WDT_MR, wdt->mr);
270 }
271 
272 #ifdef CFG_PM_ARM32
atmel_wdt_pm(enum pm_op op,uint32_t pm_hint __unused,const struct pm_callback_handle * hdl)273 static TEE_Result atmel_wdt_pm(enum pm_op op, uint32_t pm_hint __unused,
274 			       const struct pm_callback_handle *hdl)
275 {
276 	struct atmel_wdt *wdt = hdl->handle;
277 
278 	switch (op) {
279 	case PM_OP_RESUME:
280 		atmel_wdt_init_hw(wdt);
281 		if (wdt->enabled)
282 			atmel_wdt_start(wdt);
283 		break;
284 	case PM_OP_SUSPEND:
285 		if (wdt->enabled)
286 			atmel_wdt_stop(wdt);
287 		break;
288 	default:
289 		panic("Invalid PM operation");
290 	}
291 
292 	return TEE_SUCCESS;
293 }
294 
atmel_wdt_register_pm(struct atmel_wdt * wdt)295 static void atmel_wdt_register_pm(struct atmel_wdt *wdt)
296 {
297 	register_pm_driver_cb(atmel_wdt_pm, wdt, "atmel_wdt");
298 }
299 #else
atmel_wdt_register_pm(struct atmel_wdt * wdt __unused)300 static void atmel_wdt_register_pm(struct atmel_wdt *wdt __unused)
301 {
302 }
303 #endif
304 
wdt_node_probe(const void * fdt,int node,const void * compat_data)305 static TEE_Result wdt_node_probe(const void *fdt, int node,
306 				 const void *compat_data)
307 {
308 	const struct wdt_compat *compat = compat_data;
309 	size_t size = 0;
310 	struct atmel_wdt *wdt;
311 	uint32_t irq_type = 0;
312 	uint32_t irq_prio = 0;
313 	int it = DT_INFO_INVALID_INTERRUPT;
314 	struct itr_handler *it_hdlr = NULL;
315 	TEE_Result res = TEE_ERROR_GENERIC;
316 
317 	if (fdt_get_status(fdt, node) != DT_STATUS_OK_SEC)
318 		return TEE_ERROR_BAD_PARAMETERS;
319 
320 	if (compat->wdt_ps)
321 		matrix_configure_periph_secure(AT91C_ID_WDT);
322 
323 	wdt = calloc(1, sizeof(*wdt));
324 	if (!wdt)
325 		return TEE_ERROR_OUT_OF_MEMORY;
326 
327 	wdt->chip.ops = &atmel_wdt_ops;
328 	wdt->type = compat->type;
329 	wdt->dis_mask = compat->dis_mask;
330 
331 	it = dt_get_irq_type_prio(fdt, node, &irq_type, &irq_prio);
332 	if (it == DT_INFO_INVALID_INTERRUPT)
333 		goto err_free;
334 
335 	res = interrupt_alloc_add_conf_handler(interrupt_get_main_chip(),
336 					       it, atmel_wdt_itr_cb, 0, wdt,
337 					       irq_type, irq_prio, &it_hdlr);
338 	if (res)
339 		goto err_free;
340 
341 	if (dt_map_dev(fdt, node, &wdt->base, &size, DT_MAP_AUTO) < 0)
342 		goto err_remove_handler;
343 
344 	/* Get current state of the watchdog */
345 	wdt->mr = io_read32(wdt->base + WDT_MR) & wdt->dis_mask;
346 
347 	atmel_wdt_init_hw(wdt);
348 	interrupt_enable(it_hdlr->chip, it_hdlr->it);
349 
350 	res = watchdog_register(&wdt->chip);
351 	if (res)
352 		goto err_disable_unmap;
353 
354 	atmel_wdt_register_pm(wdt);
355 
356 	return TEE_SUCCESS;
357 
358 err_disable_unmap:
359 	interrupt_disable(it_hdlr->chip, it_hdlr->it);
360 	core_mmu_remove_mapping(MEM_AREA_IO_SEC, (void *)wdt->base, size);
361 err_remove_handler:
362 	interrupt_remove_free_handler(it_hdlr);
363 err_free:
364 	free(wdt);
365 
366 	return TEE_ERROR_GENERIC;
367 }
368 
369 static const struct wdt_compat sama5d2_compat = {
370 	.wdt_ps = true,
371 	.type = WDT_TYPE_WDT,
372 	.dis_mask = WDT_MR_WDDIS,
373 };
374 
375 static const struct wdt_compat sama7g5_compat = {
376 	.wdt_ps = false,
377 	.type = WDT_TYPE_DWDT,
378 	.dis_mask = WDT_MR_WDDIS_DWDT,
379 };
380 
381 static const struct dt_device_match atmel_wdt_match_table[] = {
382 	{
383 		.compatible = "atmel,sama5d4-wdt",
384 		.compat_data = &sama5d2_compat,
385 	},
386 	{
387 		.compatible = "microchip,sama7g5-wdt",
388 		.compat_data = &sama7g5_compat,
389 	},
390 	{ }
391 };
392 
393 DEFINE_DT_DRIVER(atmel_wdt_dt_driver) = {
394 	.name = "atmel_wdt",
395 	.type = DT_DRIVER_NOTYPE,
396 	.match_table = atmel_wdt_match_table,
397 	.probe = wdt_node_probe,
398 };
399