xref: /optee_os/core/drivers/atmel_wdt.c (revision 5cbd8b3a709b1a2031f59ff10b8a60aefdea8d3b)
1*5cbd8b3aSClément Léger // SPDX-License-Identifier: BSD-2-Clause
2*5cbd8b3aSClément Léger /*
3*5cbd8b3aSClément Léger  * Copyright 2022 Microchip
4*5cbd8b3aSClément Léger  */
5*5cbd8b3aSClément Léger 
6*5cbd8b3aSClément Léger #include <assert.h>
7*5cbd8b3aSClément Léger #include <drivers/clk.h>
8*5cbd8b3aSClément Léger #include <drivers/clk_dt.h>
9*5cbd8b3aSClément Léger #include <drivers/wdt.h>
10*5cbd8b3aSClément Léger #include <io.h>
11*5cbd8b3aSClément Léger #include <kernel/delay.h>
12*5cbd8b3aSClément Léger #include <kernel/dt.h>
13*5cbd8b3aSClément Léger #include <kernel/pm.h>
14*5cbd8b3aSClément Léger #include <matrix.h>
15*5cbd8b3aSClément Léger #include <sama5d2.h>
16*5cbd8b3aSClément Léger #include <tee_api_types.h>
17*5cbd8b3aSClément Léger 
18*5cbd8b3aSClément Léger #define WDT_CR			0x0
19*5cbd8b3aSClément Léger #define WDT_CR_KEY		SHIFT_U32(0xA5, 24)
20*5cbd8b3aSClément Léger #define WDT_CR_WDRSTT		BIT(0)
21*5cbd8b3aSClément Léger 
22*5cbd8b3aSClément Léger #define WDT_MR			0x4
23*5cbd8b3aSClément Léger #define WDT_MR_WDV		GENMASK_32(11, 0)
24*5cbd8b3aSClément Léger #define WDT_MR_WDV_SET(val)	((val) & WDT_MR_WDV)
25*5cbd8b3aSClément Léger #define WDT_MR_WDFIEN		BIT(12)
26*5cbd8b3aSClément Léger #define WDT_MR_WDRSTEN		BIT(13)
27*5cbd8b3aSClément Léger #define WDT_MR_WDDIS		BIT(15)
28*5cbd8b3aSClément Léger #define WDT_MR_WDD_SHIFT	16
29*5cbd8b3aSClément Léger #define WDT_MR_WDD_MASK		GENMASK_32(11, 0)
30*5cbd8b3aSClément Léger #define WDT_MR_WDD		SHIFT_U32(WDT_MR_WDD_MASK, WDT_MR_WDD_SHIFT)
31*5cbd8b3aSClément Léger #define WDT_MR_WDD_SET(val) \
32*5cbd8b3aSClément Léger 			SHIFT_U32(((val) & WDT_MR_WDD_MASK), WDT_MR_WDD_SHIFT)
33*5cbd8b3aSClément Léger #define WDT_MR_WDDBGHLT		BIT(28)
34*5cbd8b3aSClément Léger #define WDT_MR_WDIDLEHLT	BIT(29)
35*5cbd8b3aSClément Léger 
36*5cbd8b3aSClément Léger #define WDT_SR			0x8
37*5cbd8b3aSClément Léger #define WDT_SR_DUNF		BIT(0)
38*5cbd8b3aSClément Léger #define WDT_SR_DERR		BIT(1)
39*5cbd8b3aSClément Léger 
40*5cbd8b3aSClément Léger /*
41*5cbd8b3aSClément Léger  * The watchdog is clocked by a 32768Hz clock/128 and the counter is on
42*5cbd8b3aSClément Léger  * 12 bits.
43*5cbd8b3aSClément Léger  */
44*5cbd8b3aSClément Léger #define SLOW_CLOCK_FREQ		(32768)
45*5cbd8b3aSClément Léger #define WDT_CLOCK_FREQ		(SLOW_CLOCK_FREQ / 128)
46*5cbd8b3aSClément Léger #define WDT_MIN_TIMEOUT		1
47*5cbd8b3aSClément Léger #define WDT_MAX_TIMEOUT		(BIT(12) / WDT_CLOCK_FREQ)
48*5cbd8b3aSClément Léger 
49*5cbd8b3aSClément Léger #define WDT_DEFAULT_TIMEOUT	WDT_MAX_TIMEOUT
50*5cbd8b3aSClément Léger 
51*5cbd8b3aSClément Léger /*
52*5cbd8b3aSClément Léger  * We must wait at least 3 clocks period before accessing registers MR and CR.
53*5cbd8b3aSClément Léger  * Ensure that we see at least 4 edges
54*5cbd8b3aSClément Léger  */
55*5cbd8b3aSClément Léger #define WDT_REG_ACCESS_UDELAY	(1000000ULL / SLOW_CLOCK_FREQ * 4)
56*5cbd8b3aSClément Léger 
57*5cbd8b3aSClément Léger #define SEC_TO_WDT(sec)		(((sec) * WDT_CLOCK_FREQ) - 1)
58*5cbd8b3aSClément Léger 
59*5cbd8b3aSClément Léger #define WDT_ENABLED(mr)		(!((mr) & WDT_MR_WDDIS))
60*5cbd8b3aSClément Léger 
61*5cbd8b3aSClément Léger struct atmel_wdt {
62*5cbd8b3aSClément Léger 	struct wdt_chip chip;
63*5cbd8b3aSClément Léger 	vaddr_t base;
64*5cbd8b3aSClément Léger 	unsigned long rate;
65*5cbd8b3aSClément Léger 	uint32_t mr;
66*5cbd8b3aSClément Léger 	bool enabled;
67*5cbd8b3aSClément Léger };
68*5cbd8b3aSClément Léger 
69*5cbd8b3aSClément Léger static void atmel_wdt_write_sleep(struct atmel_wdt *wdt, uint32_t reg,
70*5cbd8b3aSClément Léger 				  uint32_t val)
71*5cbd8b3aSClément Léger {
72*5cbd8b3aSClément Léger 	udelay(WDT_REG_ACCESS_UDELAY);
73*5cbd8b3aSClément Léger 
74*5cbd8b3aSClément Léger 	io_write32(wdt->base + reg, val);
75*5cbd8b3aSClément Léger }
76*5cbd8b3aSClément Léger 
77*5cbd8b3aSClément Léger static TEE_Result atmel_wdt_settimeout(struct wdt_chip *chip,
78*5cbd8b3aSClément Léger 				       unsigned long timeout)
79*5cbd8b3aSClément Léger {
80*5cbd8b3aSClément Léger 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
81*5cbd8b3aSClément Léger 
82*5cbd8b3aSClément Léger 	wdt->mr &= ~WDT_MR_WDV;
83*5cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDV_SET(SEC_TO_WDT(timeout));
84*5cbd8b3aSClément Léger 
85*5cbd8b3aSClément Léger 	/* WDV and WDD can only be updated when the watchdog is running */
86*5cbd8b3aSClément Léger 	if (WDT_ENABLED(wdt->mr))
87*5cbd8b3aSClément Léger 		atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
88*5cbd8b3aSClément Léger 
89*5cbd8b3aSClément Léger 	return TEE_SUCCESS;
90*5cbd8b3aSClément Léger }
91*5cbd8b3aSClément Léger 
92*5cbd8b3aSClément Léger static void atmel_wdt_ping(struct wdt_chip *chip)
93*5cbd8b3aSClément Léger {
94*5cbd8b3aSClément Léger 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
95*5cbd8b3aSClément Léger 
96*5cbd8b3aSClément Léger 	atmel_wdt_write_sleep(wdt, WDT_CR, WDT_CR_KEY | WDT_CR_WDRSTT);
97*5cbd8b3aSClément Léger }
98*5cbd8b3aSClément Léger 
99*5cbd8b3aSClément Léger static void atmel_wdt_start(struct atmel_wdt *wdt)
100*5cbd8b3aSClément Léger {
101*5cbd8b3aSClément Léger 	wdt->mr &= ~WDT_MR_WDDIS;
102*5cbd8b3aSClément Léger 	atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
103*5cbd8b3aSClément Léger }
104*5cbd8b3aSClément Léger 
105*5cbd8b3aSClément Léger static void atmel_wdt_enable(struct wdt_chip *chip)
106*5cbd8b3aSClément Léger {
107*5cbd8b3aSClément Léger 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
108*5cbd8b3aSClément Léger 
109*5cbd8b3aSClément Léger 	wdt->enabled = true;
110*5cbd8b3aSClément Léger 	atmel_wdt_start(wdt);
111*5cbd8b3aSClément Léger }
112*5cbd8b3aSClément Léger 
113*5cbd8b3aSClément Léger static void atmel_wdt_stop(struct atmel_wdt *wdt)
114*5cbd8b3aSClément Léger {
115*5cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDDIS;
116*5cbd8b3aSClément Léger 	atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
117*5cbd8b3aSClément Léger }
118*5cbd8b3aSClément Léger 
119*5cbd8b3aSClément Léger static void atmel_wdt_disable(struct wdt_chip *chip)
120*5cbd8b3aSClément Léger {
121*5cbd8b3aSClément Léger 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
122*5cbd8b3aSClément Léger 
123*5cbd8b3aSClément Léger 	wdt->enabled = false;
124*5cbd8b3aSClément Léger 	atmel_wdt_stop(wdt);
125*5cbd8b3aSClément Léger }
126*5cbd8b3aSClément Léger 
127*5cbd8b3aSClément Léger static enum itr_return atmel_wdt_itr_cb(struct itr_handler *h)
128*5cbd8b3aSClément Léger {
129*5cbd8b3aSClément Léger 	struct atmel_wdt *wdt = h->data;
130*5cbd8b3aSClément Léger 	uint32_t sr = io_read32(wdt->base + WDT_SR);
131*5cbd8b3aSClément Léger 
132*5cbd8b3aSClément Léger 	if (sr & WDT_SR_DUNF)
133*5cbd8b3aSClément Léger 		DMSG("Watchdog Underflow !");
134*5cbd8b3aSClément Léger 	if (sr & WDT_SR_DERR)
135*5cbd8b3aSClément Léger 		DMSG("Watchdog Error !");
136*5cbd8b3aSClément Léger 
137*5cbd8b3aSClément Léger 	panic("Watchdog interrupt");
138*5cbd8b3aSClément Léger 
139*5cbd8b3aSClément Léger 	return ITRR_HANDLED;
140*5cbd8b3aSClément Léger }
141*5cbd8b3aSClément Léger 
142*5cbd8b3aSClément Léger static TEE_Result atmel_wdt_init(struct wdt_chip *chip __unused,
143*5cbd8b3aSClément Léger 				 unsigned long *min_timeout,
144*5cbd8b3aSClément Léger 				 unsigned long *max_timeout)
145*5cbd8b3aSClément Léger {
146*5cbd8b3aSClément Léger 	*min_timeout = WDT_MIN_TIMEOUT;
147*5cbd8b3aSClément Léger 	*max_timeout = WDT_MAX_TIMEOUT;
148*5cbd8b3aSClément Léger 
149*5cbd8b3aSClément Léger 	return TEE_SUCCESS;
150*5cbd8b3aSClément Léger }
151*5cbd8b3aSClément Léger 
152*5cbd8b3aSClément Léger static const struct wdt_ops atmel_wdt_ops = {
153*5cbd8b3aSClément Léger 	.init = atmel_wdt_init,
154*5cbd8b3aSClément Léger 	.start = atmel_wdt_enable,
155*5cbd8b3aSClément Léger 	.stop = atmel_wdt_disable,
156*5cbd8b3aSClément Léger 	.ping = atmel_wdt_ping,
157*5cbd8b3aSClément Léger 	.set_timeout = atmel_wdt_settimeout,
158*5cbd8b3aSClément Léger };
159*5cbd8b3aSClément Léger 
160*5cbd8b3aSClément Léger static void atmel_wdt_init_hw(struct atmel_wdt *wdt)
161*5cbd8b3aSClément Léger {
162*5cbd8b3aSClément Léger 	uint32_t mr = 0;
163*5cbd8b3aSClément Léger 
164*5cbd8b3aSClément Léger 	/*
165*5cbd8b3aSClément Léger 	 * If we are resuming, we disabled the watchdog on suspend but the
166*5cbd8b3aSClément Léger 	 * bootloader might have enabled the watchdog. If so, disable it
167*5cbd8b3aSClément Léger 	 * properly.
168*5cbd8b3aSClément Léger 	 */
169*5cbd8b3aSClément Léger 	if (!WDT_ENABLED(wdt->mr)) {
170*5cbd8b3aSClément Léger 		mr = io_read32(wdt->base + WDT_MR);
171*5cbd8b3aSClément Léger 		if (WDT_ENABLED(mr))
172*5cbd8b3aSClément Léger 			io_write32(wdt->base + WDT_MR, mr | WDT_MR_WDDIS);
173*5cbd8b3aSClément Léger 	}
174*5cbd8b3aSClément Léger 
175*5cbd8b3aSClément Léger 	/* Enable interrupt, and disable watchdog in debug and idle */
176*5cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDFIEN | WDT_MR_WDDBGHLT | WDT_MR_WDIDLEHLT;
177*5cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDD_SET(SEC_TO_WDT(WDT_MAX_TIMEOUT));
178*5cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDV_SET(SEC_TO_WDT(WDT_DEFAULT_TIMEOUT));
179*5cbd8b3aSClément Léger 
180*5cbd8b3aSClément Léger 	/*
181*5cbd8b3aSClément Léger 	 * If the watchdog was enabled, write the configuration which will ping
182*5cbd8b3aSClément Léger 	 * the watchdog.
183*5cbd8b3aSClément Léger 	 */
184*5cbd8b3aSClément Léger 	if (WDT_ENABLED(wdt->mr))
185*5cbd8b3aSClément Léger 		io_write32(wdt->base + WDT_MR, wdt->mr);
186*5cbd8b3aSClément Léger }
187*5cbd8b3aSClément Léger 
188*5cbd8b3aSClément Léger #ifdef CFG_PM_ARM32
189*5cbd8b3aSClément Léger static TEE_Result atmel_wdt_pm(enum pm_op op, uint32_t pm_hint __unused,
190*5cbd8b3aSClément Léger 			       const struct pm_callback_handle *hdl)
191*5cbd8b3aSClément Léger {
192*5cbd8b3aSClément Léger 	struct atmel_wdt *wdt = hdl->handle;
193*5cbd8b3aSClément Léger 
194*5cbd8b3aSClément Léger 	switch (op) {
195*5cbd8b3aSClément Léger 	case PM_OP_RESUME:
196*5cbd8b3aSClément Léger 		atmel_wdt_init_hw(wdt);
197*5cbd8b3aSClément Léger 		if (wdt->enabled)
198*5cbd8b3aSClément Léger 			atmel_wdt_start(wdt);
199*5cbd8b3aSClément Léger 		break;
200*5cbd8b3aSClément Léger 	case PM_OP_SUSPEND:
201*5cbd8b3aSClément Léger 		if (wdt->enabled)
202*5cbd8b3aSClément Léger 			atmel_wdt_stop(wdt);
203*5cbd8b3aSClément Léger 		break;
204*5cbd8b3aSClément Léger 	default:
205*5cbd8b3aSClément Léger 		panic("Invalid PM operation");
206*5cbd8b3aSClément Léger 	}
207*5cbd8b3aSClément Léger 
208*5cbd8b3aSClément Léger 	return TEE_SUCCESS;
209*5cbd8b3aSClément Léger }
210*5cbd8b3aSClément Léger 
211*5cbd8b3aSClément Léger static void atmel_wdt_register_pm(struct atmel_wdt *wdt)
212*5cbd8b3aSClément Léger {
213*5cbd8b3aSClément Léger 	register_pm_driver_cb(atmel_wdt_pm, wdt, "atmel_wdt");
214*5cbd8b3aSClément Léger }
215*5cbd8b3aSClément Léger #else
216*5cbd8b3aSClément Léger static void atmel_wdt_register_pm(struct atmel_wdt *wdt __unused)
217*5cbd8b3aSClément Léger {
218*5cbd8b3aSClément Léger }
219*5cbd8b3aSClément Léger #endif
220*5cbd8b3aSClément Léger 
221*5cbd8b3aSClément Léger static TEE_Result wdt_node_probe(const void *fdt, int node,
222*5cbd8b3aSClément Léger 				 const void *compat_data __unused)
223*5cbd8b3aSClément Léger {
224*5cbd8b3aSClément Léger 	size_t size = 0;
225*5cbd8b3aSClément Léger 	struct atmel_wdt *wdt;
226*5cbd8b3aSClément Léger 	uint32_t irq_type = 0;
227*5cbd8b3aSClément Léger 	uint32_t irq_prio = 0;
228*5cbd8b3aSClément Léger 	int it = DT_INFO_INVALID_INTERRUPT;
229*5cbd8b3aSClément Léger 	struct itr_handler *it_hdlr;
230*5cbd8b3aSClément Léger 
231*5cbd8b3aSClément Léger 	if (_fdt_get_status(fdt, node) != DT_STATUS_OK_SEC)
232*5cbd8b3aSClément Léger 		return TEE_ERROR_BAD_PARAMETERS;
233*5cbd8b3aSClément Léger 
234*5cbd8b3aSClément Léger 	matrix_configure_periph_secure(AT91C_ID_WDT);
235*5cbd8b3aSClément Léger 
236*5cbd8b3aSClément Léger 	wdt = calloc(1, sizeof(*wdt));
237*5cbd8b3aSClément Léger 	if (!wdt)
238*5cbd8b3aSClément Léger 		return TEE_ERROR_OUT_OF_MEMORY;
239*5cbd8b3aSClément Léger 
240*5cbd8b3aSClément Léger 	wdt->chip.ops = &atmel_wdt_ops;
241*5cbd8b3aSClément Léger 
242*5cbd8b3aSClément Léger 	it = dt_get_irq_type_prio(fdt, node, &irq_type, &irq_prio);
243*5cbd8b3aSClément Léger 	if (it == DT_INFO_INVALID_INTERRUPT)
244*5cbd8b3aSClément Léger 		goto err_free_wdt;
245*5cbd8b3aSClément Léger 
246*5cbd8b3aSClément Léger 	it_hdlr = itr_alloc_add_type_prio(it, &atmel_wdt_itr_cb, 0, wdt,
247*5cbd8b3aSClément Léger 					  irq_type, irq_prio);
248*5cbd8b3aSClément Léger 	if (!it_hdlr)
249*5cbd8b3aSClément Léger 		goto err_free_wdt;
250*5cbd8b3aSClément Léger 
251*5cbd8b3aSClément Léger 	if (dt_map_dev(fdt, node, &wdt->base, &size) < 0)
252*5cbd8b3aSClément Léger 		goto err_free_itr_handler;
253*5cbd8b3aSClément Léger 
254*5cbd8b3aSClément Léger 	/* Get current state of the watchdog */
255*5cbd8b3aSClément Léger 	wdt->mr = io_read32(wdt->base + WDT_MR) & WDT_MR_WDDIS;
256*5cbd8b3aSClément Léger 
257*5cbd8b3aSClément Léger 	atmel_wdt_init_hw(wdt);
258*5cbd8b3aSClément Léger 	itr_enable(it);
259*5cbd8b3aSClément Léger 	atmel_wdt_register_pm(wdt);
260*5cbd8b3aSClément Léger 
261*5cbd8b3aSClément Léger 	return watchdog_register(&wdt->chip);
262*5cbd8b3aSClément Léger 
263*5cbd8b3aSClément Léger err_free_itr_handler:
264*5cbd8b3aSClément Léger 	itr_free(it_hdlr);
265*5cbd8b3aSClément Léger err_free_wdt:
266*5cbd8b3aSClément Léger 	free(wdt);
267*5cbd8b3aSClément Léger 
268*5cbd8b3aSClément Léger 	return TEE_ERROR_GENERIC;
269*5cbd8b3aSClément Léger }
270*5cbd8b3aSClément Léger 
271*5cbd8b3aSClément Léger static const struct dt_device_match atmel_wdt_match_table[] = {
272*5cbd8b3aSClément Léger 	{ .compatible = "atmel,sama5d4-wdt" },
273*5cbd8b3aSClément Léger 	{ }
274*5cbd8b3aSClément Léger };
275*5cbd8b3aSClément Léger 
276*5cbd8b3aSClément Léger DEFINE_DT_DRIVER(atmel_wdt_dt_driver) = {
277*5cbd8b3aSClément Léger 	.name = "atmel_wdt",
278*5cbd8b3aSClément Léger 	.type = DT_DRIVER_NOTYPE,
279*5cbd8b3aSClément Léger 	.match_table = atmel_wdt_match_table,
280*5cbd8b3aSClément Léger 	.probe = wdt_node_probe,
281*5cbd8b3aSClément Léger };
282