xref: /optee_os/core/drivers/atmel_wdt.c (revision c64c658bbed6d7675194b459b919c0b2ab5af039)
15cbd8b3aSClément Léger // SPDX-License-Identifier: BSD-2-Clause
25cbd8b3aSClément Léger /*
35cbd8b3aSClément Léger  * Copyright 2022 Microchip
45cbd8b3aSClément Léger  */
55cbd8b3aSClément Léger 
65cbd8b3aSClément Léger #include <assert.h>
75cbd8b3aSClément Léger #include <drivers/clk.h>
85cbd8b3aSClément Léger #include <drivers/clk_dt.h>
95cbd8b3aSClément Léger #include <drivers/wdt.h>
105cbd8b3aSClément Léger #include <io.h>
115cbd8b3aSClément Léger #include <kernel/delay.h>
125cbd8b3aSClément Léger #include <kernel/dt.h>
139e3c57c8SEtienne Carriere #include <kernel/dt_driver.h>
145cbd8b3aSClément Léger #include <kernel/pm.h>
155cbd8b3aSClément Léger #include <matrix.h>
16*c64c658bSEtienne Carriere #include <mm/core_mmu.h>
175cbd8b3aSClément Léger #include <sama5d2.h>
185cbd8b3aSClément Léger #include <tee_api_types.h>
195cbd8b3aSClément Léger 
205cbd8b3aSClément Léger #define WDT_CR			0x0
215cbd8b3aSClément Léger #define WDT_CR_KEY		SHIFT_U32(0xA5, 24)
225cbd8b3aSClément Léger #define WDT_CR_WDRSTT		BIT(0)
235cbd8b3aSClément Léger 
245cbd8b3aSClément Léger #define WDT_MR			0x4
255cbd8b3aSClément Léger #define WDT_MR_WDV		GENMASK_32(11, 0)
265cbd8b3aSClément Léger #define WDT_MR_WDV_SET(val)	((val) & WDT_MR_WDV)
275cbd8b3aSClément Léger #define WDT_MR_WDFIEN		BIT(12)
285cbd8b3aSClément Léger #define WDT_MR_WDRSTEN		BIT(13)
295cbd8b3aSClément Léger #define WDT_MR_WDDIS		BIT(15)
305cbd8b3aSClément Léger #define WDT_MR_WDD_SHIFT	16
315cbd8b3aSClément Léger #define WDT_MR_WDD_MASK		GENMASK_32(11, 0)
325cbd8b3aSClément Léger #define WDT_MR_WDD		SHIFT_U32(WDT_MR_WDD_MASK, WDT_MR_WDD_SHIFT)
335cbd8b3aSClément Léger #define WDT_MR_WDD_SET(val) \
345cbd8b3aSClément Léger 			SHIFT_U32(((val) & WDT_MR_WDD_MASK), WDT_MR_WDD_SHIFT)
355cbd8b3aSClément Léger #define WDT_MR_WDDBGHLT		BIT(28)
365cbd8b3aSClément Léger #define WDT_MR_WDIDLEHLT	BIT(29)
375cbd8b3aSClément Léger 
385cbd8b3aSClément Léger #define WDT_SR			0x8
395cbd8b3aSClément Léger #define WDT_SR_DUNF		BIT(0)
405cbd8b3aSClément Léger #define WDT_SR_DERR		BIT(1)
415cbd8b3aSClément Léger 
425cbd8b3aSClément Léger /*
435cbd8b3aSClément Léger  * The watchdog is clocked by a 32768Hz clock/128 and the counter is on
445cbd8b3aSClément Léger  * 12 bits.
455cbd8b3aSClément Léger  */
465cbd8b3aSClément Léger #define SLOW_CLOCK_FREQ		(32768)
475cbd8b3aSClément Léger #define WDT_CLOCK_FREQ		(SLOW_CLOCK_FREQ / 128)
485cbd8b3aSClément Léger #define WDT_MIN_TIMEOUT		1
495cbd8b3aSClément Léger #define WDT_MAX_TIMEOUT		(BIT(12) / WDT_CLOCK_FREQ)
505cbd8b3aSClément Léger 
515cbd8b3aSClément Léger #define WDT_DEFAULT_TIMEOUT	WDT_MAX_TIMEOUT
525cbd8b3aSClément Léger 
535cbd8b3aSClément Léger /*
545cbd8b3aSClément Léger  * We must wait at least 3 clocks period before accessing registers MR and CR.
555cbd8b3aSClément Léger  * Ensure that we see at least 4 edges
565cbd8b3aSClément Léger  */
575cbd8b3aSClément Léger #define WDT_REG_ACCESS_UDELAY	(1000000ULL / SLOW_CLOCK_FREQ * 4)
585cbd8b3aSClément Léger 
595cbd8b3aSClément Léger #define SEC_TO_WDT(sec)		(((sec) * WDT_CLOCK_FREQ) - 1)
605cbd8b3aSClément Léger 
615cbd8b3aSClément Léger #define WDT_ENABLED(mr)		(!((mr) & WDT_MR_WDDIS))
625cbd8b3aSClément Léger 
635cbd8b3aSClément Léger struct atmel_wdt {
645cbd8b3aSClément Léger 	struct wdt_chip chip;
655cbd8b3aSClément Léger 	vaddr_t base;
665cbd8b3aSClément Léger 	unsigned long rate;
675cbd8b3aSClément Léger 	uint32_t mr;
685cbd8b3aSClément Léger 	bool enabled;
695cbd8b3aSClément Léger };
705cbd8b3aSClément Léger 
715cbd8b3aSClément Léger static void atmel_wdt_write_sleep(struct atmel_wdt *wdt, uint32_t reg,
725cbd8b3aSClément Léger 				  uint32_t val)
735cbd8b3aSClément Léger {
745cbd8b3aSClément Léger 	udelay(WDT_REG_ACCESS_UDELAY);
755cbd8b3aSClément Léger 
765cbd8b3aSClément Léger 	io_write32(wdt->base + reg, val);
775cbd8b3aSClément Léger }
785cbd8b3aSClément Léger 
795cbd8b3aSClément Léger static TEE_Result atmel_wdt_settimeout(struct wdt_chip *chip,
805cbd8b3aSClément Léger 				       unsigned long timeout)
815cbd8b3aSClément Léger {
825cbd8b3aSClément Léger 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
835cbd8b3aSClément Léger 
845cbd8b3aSClément Léger 	wdt->mr &= ~WDT_MR_WDV;
855cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDV_SET(SEC_TO_WDT(timeout));
865cbd8b3aSClément Léger 
875cbd8b3aSClément Léger 	/* WDV and WDD can only be updated when the watchdog is running */
885cbd8b3aSClément Léger 	if (WDT_ENABLED(wdt->mr))
895cbd8b3aSClément Léger 		atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
905cbd8b3aSClément Léger 
915cbd8b3aSClément Léger 	return TEE_SUCCESS;
925cbd8b3aSClément Léger }
935cbd8b3aSClément Léger 
945cbd8b3aSClément Léger static void atmel_wdt_ping(struct wdt_chip *chip)
955cbd8b3aSClément Léger {
965cbd8b3aSClément Léger 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
975cbd8b3aSClément Léger 
985cbd8b3aSClément Léger 	atmel_wdt_write_sleep(wdt, WDT_CR, WDT_CR_KEY | WDT_CR_WDRSTT);
995cbd8b3aSClément Léger }
1005cbd8b3aSClément Léger 
1015cbd8b3aSClément Léger static void atmel_wdt_start(struct atmel_wdt *wdt)
1025cbd8b3aSClément Léger {
1035cbd8b3aSClément Léger 	wdt->mr &= ~WDT_MR_WDDIS;
1045cbd8b3aSClément Léger 	atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
1055cbd8b3aSClément Léger }
1065cbd8b3aSClément Léger 
1075cbd8b3aSClément Léger static void atmel_wdt_enable(struct wdt_chip *chip)
1085cbd8b3aSClément Léger {
1095cbd8b3aSClément Léger 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
1105cbd8b3aSClément Léger 
1115cbd8b3aSClément Léger 	wdt->enabled = true;
1125cbd8b3aSClément Léger 	atmel_wdt_start(wdt);
1135cbd8b3aSClément Léger }
1145cbd8b3aSClément Léger 
1155cbd8b3aSClément Léger static void atmel_wdt_stop(struct atmel_wdt *wdt)
1165cbd8b3aSClément Léger {
1175cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDDIS;
1185cbd8b3aSClément Léger 	atmel_wdt_write_sleep(wdt, WDT_MR, wdt->mr);
1195cbd8b3aSClément Léger }
1205cbd8b3aSClément Léger 
1215cbd8b3aSClément Léger static void atmel_wdt_disable(struct wdt_chip *chip)
1225cbd8b3aSClément Léger {
1235cbd8b3aSClément Léger 	struct atmel_wdt *wdt = container_of(chip, struct atmel_wdt, chip);
1245cbd8b3aSClément Léger 
1255cbd8b3aSClément Léger 	wdt->enabled = false;
1265cbd8b3aSClément Léger 	atmel_wdt_stop(wdt);
1275cbd8b3aSClément Léger }
1285cbd8b3aSClément Léger 
1295cbd8b3aSClément Léger static enum itr_return atmel_wdt_itr_cb(struct itr_handler *h)
1305cbd8b3aSClément Léger {
1315cbd8b3aSClément Léger 	struct atmel_wdt *wdt = h->data;
1325cbd8b3aSClément Léger 	uint32_t sr = io_read32(wdt->base + WDT_SR);
1335cbd8b3aSClément Léger 
1345cbd8b3aSClément Léger 	if (sr & WDT_SR_DUNF)
1355cbd8b3aSClément Léger 		DMSG("Watchdog Underflow !");
1365cbd8b3aSClément Léger 	if (sr & WDT_SR_DERR)
1375cbd8b3aSClément Léger 		DMSG("Watchdog Error !");
1385cbd8b3aSClément Léger 
1395cbd8b3aSClément Léger 	panic("Watchdog interrupt");
1405cbd8b3aSClément Léger 
1415cbd8b3aSClément Léger 	return ITRR_HANDLED;
1425cbd8b3aSClément Léger }
1435cbd8b3aSClément Léger 
1445cbd8b3aSClément Léger static TEE_Result atmel_wdt_init(struct wdt_chip *chip __unused,
1455cbd8b3aSClément Léger 				 unsigned long *min_timeout,
1465cbd8b3aSClément Léger 				 unsigned long *max_timeout)
1475cbd8b3aSClément Léger {
1485cbd8b3aSClément Léger 	*min_timeout = WDT_MIN_TIMEOUT;
1495cbd8b3aSClément Léger 	*max_timeout = WDT_MAX_TIMEOUT;
1505cbd8b3aSClément Léger 
1515cbd8b3aSClément Léger 	return TEE_SUCCESS;
1525cbd8b3aSClément Léger }
1535cbd8b3aSClément Léger 
1545cbd8b3aSClément Léger static const struct wdt_ops atmel_wdt_ops = {
1555cbd8b3aSClément Léger 	.init = atmel_wdt_init,
1565cbd8b3aSClément Léger 	.start = atmel_wdt_enable,
1575cbd8b3aSClément Léger 	.stop = atmel_wdt_disable,
1585cbd8b3aSClément Léger 	.ping = atmel_wdt_ping,
1595cbd8b3aSClément Léger 	.set_timeout = atmel_wdt_settimeout,
1605cbd8b3aSClément Léger };
1615cbd8b3aSClément Léger 
1625cbd8b3aSClément Léger static void atmel_wdt_init_hw(struct atmel_wdt *wdt)
1635cbd8b3aSClément Léger {
1645cbd8b3aSClément Léger 	uint32_t mr = 0;
1655cbd8b3aSClément Léger 
1665cbd8b3aSClément Léger 	/*
1675cbd8b3aSClément Léger 	 * If we are resuming, we disabled the watchdog on suspend but the
1685cbd8b3aSClément Léger 	 * bootloader might have enabled the watchdog. If so, disable it
1695cbd8b3aSClément Léger 	 * properly.
1705cbd8b3aSClément Léger 	 */
1715cbd8b3aSClément Léger 	if (!WDT_ENABLED(wdt->mr)) {
1725cbd8b3aSClément Léger 		mr = io_read32(wdt->base + WDT_MR);
1735cbd8b3aSClément Léger 		if (WDT_ENABLED(mr))
1745cbd8b3aSClément Léger 			io_write32(wdt->base + WDT_MR, mr | WDT_MR_WDDIS);
1755cbd8b3aSClément Léger 	}
1765cbd8b3aSClément Léger 
1775cbd8b3aSClément Léger 	/* Enable interrupt, and disable watchdog in debug and idle */
1785cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDFIEN | WDT_MR_WDDBGHLT | WDT_MR_WDIDLEHLT;
179d5d94b35SClément Léger 	/* Enable watchdog reset */
180d5d94b35SClément Léger 	wdt->mr |= WDT_MR_WDRSTEN;
1815cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDD_SET(SEC_TO_WDT(WDT_MAX_TIMEOUT));
1825cbd8b3aSClément Léger 	wdt->mr |= WDT_MR_WDV_SET(SEC_TO_WDT(WDT_DEFAULT_TIMEOUT));
1835cbd8b3aSClément Léger 
1845cbd8b3aSClément Léger 	/*
1855cbd8b3aSClément Léger 	 * If the watchdog was enabled, write the configuration which will ping
1865cbd8b3aSClément Léger 	 * the watchdog.
1875cbd8b3aSClément Léger 	 */
1885cbd8b3aSClément Léger 	if (WDT_ENABLED(wdt->mr))
1895cbd8b3aSClément Léger 		io_write32(wdt->base + WDT_MR, wdt->mr);
1905cbd8b3aSClément Léger }
1915cbd8b3aSClément Léger 
1925cbd8b3aSClément Léger #ifdef CFG_PM_ARM32
1935cbd8b3aSClément Léger static TEE_Result atmel_wdt_pm(enum pm_op op, uint32_t pm_hint __unused,
1945cbd8b3aSClément Léger 			       const struct pm_callback_handle *hdl)
1955cbd8b3aSClément Léger {
1965cbd8b3aSClément Léger 	struct atmel_wdt *wdt = hdl->handle;
1975cbd8b3aSClément Léger 
1985cbd8b3aSClément Léger 	switch (op) {
1995cbd8b3aSClément Léger 	case PM_OP_RESUME:
2005cbd8b3aSClément Léger 		atmel_wdt_init_hw(wdt);
2015cbd8b3aSClément Léger 		if (wdt->enabled)
2025cbd8b3aSClément Léger 			atmel_wdt_start(wdt);
2035cbd8b3aSClément Léger 		break;
2045cbd8b3aSClément Léger 	case PM_OP_SUSPEND:
2055cbd8b3aSClément Léger 		if (wdt->enabled)
2065cbd8b3aSClément Léger 			atmel_wdt_stop(wdt);
2075cbd8b3aSClément Léger 		break;
2085cbd8b3aSClément Léger 	default:
2095cbd8b3aSClément Léger 		panic("Invalid PM operation");
2105cbd8b3aSClément Léger 	}
2115cbd8b3aSClément Léger 
2125cbd8b3aSClément Léger 	return TEE_SUCCESS;
2135cbd8b3aSClément Léger }
2145cbd8b3aSClément Léger 
2155cbd8b3aSClément Léger static void atmel_wdt_register_pm(struct atmel_wdt *wdt)
2165cbd8b3aSClément Léger {
2175cbd8b3aSClément Léger 	register_pm_driver_cb(atmel_wdt_pm, wdt, "atmel_wdt");
2185cbd8b3aSClément Léger }
2195cbd8b3aSClément Léger #else
2205cbd8b3aSClément Léger static void atmel_wdt_register_pm(struct atmel_wdt *wdt __unused)
2215cbd8b3aSClément Léger {
2225cbd8b3aSClément Léger }
2235cbd8b3aSClément Léger #endif
2245cbd8b3aSClément Léger 
2255cbd8b3aSClément Léger static TEE_Result wdt_node_probe(const void *fdt, int node,
2265cbd8b3aSClément Léger 				 const void *compat_data __unused)
2275cbd8b3aSClément Léger {
2285cbd8b3aSClément Léger 	size_t size = 0;
2295cbd8b3aSClément Léger 	struct atmel_wdt *wdt;
2305cbd8b3aSClément Léger 	uint32_t irq_type = 0;
2315cbd8b3aSClément Léger 	uint32_t irq_prio = 0;
2325cbd8b3aSClément Léger 	int it = DT_INFO_INVALID_INTERRUPT;
233*c64c658bSEtienne Carriere 	struct itr_handler *it_hdlr = NULL;
234*c64c658bSEtienne Carriere 	TEE_Result res = TEE_ERROR_GENERIC;
2355cbd8b3aSClément Léger 
236f354a5d8SGatien Chevallier 	if (fdt_get_status(fdt, node) != DT_STATUS_OK_SEC)
2375cbd8b3aSClément Léger 		return TEE_ERROR_BAD_PARAMETERS;
2385cbd8b3aSClément Léger 
2395cbd8b3aSClément Léger 	matrix_configure_periph_secure(AT91C_ID_WDT);
2405cbd8b3aSClément Léger 
2415cbd8b3aSClément Léger 	wdt = calloc(1, sizeof(*wdt));
2425cbd8b3aSClément Léger 	if (!wdt)
2435cbd8b3aSClément Léger 		return TEE_ERROR_OUT_OF_MEMORY;
2445cbd8b3aSClément Léger 
2455cbd8b3aSClément Léger 	wdt->chip.ops = &atmel_wdt_ops;
2465cbd8b3aSClément Léger 
2475cbd8b3aSClément Léger 	it = dt_get_irq_type_prio(fdt, node, &irq_type, &irq_prio);
2485cbd8b3aSClément Léger 	if (it == DT_INFO_INVALID_INTERRUPT)
249*c64c658bSEtienne Carriere 		goto err_free;
2505cbd8b3aSClément Léger 
251*c64c658bSEtienne Carriere 	res = interrupt_alloc_add_conf_handler(interrupt_get_main_chip(),
252*c64c658bSEtienne Carriere 					       it, atmel_wdt_itr_cb, 0, wdt,
253*c64c658bSEtienne Carriere 					       irq_type, irq_prio, &it_hdlr);
254*c64c658bSEtienne Carriere 	if (res)
255*c64c658bSEtienne Carriere 		goto err_free;
2565cbd8b3aSClément Léger 
257a5d5bbc8SVesa Jääskeläinen 	if (dt_map_dev(fdt, node, &wdt->base, &size, DT_MAP_AUTO) < 0)
258*c64c658bSEtienne Carriere 		goto err_remove_handler;
2595cbd8b3aSClément Léger 
2605cbd8b3aSClément Léger 	/* Get current state of the watchdog */
2615cbd8b3aSClément Léger 	wdt->mr = io_read32(wdt->base + WDT_MR) & WDT_MR_WDDIS;
2625cbd8b3aSClément Léger 
2635cbd8b3aSClément Léger 	atmel_wdt_init_hw(wdt);
264*c64c658bSEtienne Carriere 	interrupt_enable(it_hdlr->chip, it_hdlr->it);
265*c64c658bSEtienne Carriere 
266*c64c658bSEtienne Carriere 	res = watchdog_register(&wdt->chip);
267*c64c658bSEtienne Carriere 	if (res)
268*c64c658bSEtienne Carriere 		goto err_disable_unmap;
269*c64c658bSEtienne Carriere 
2705cbd8b3aSClément Léger 	atmel_wdt_register_pm(wdt);
2715cbd8b3aSClément Léger 
272*c64c658bSEtienne Carriere 	return TEE_SUCCESS;
2735cbd8b3aSClément Léger 
274*c64c658bSEtienne Carriere err_disable_unmap:
275*c64c658bSEtienne Carriere 	interrupt_disable(it_hdlr->chip, it_hdlr->it);
276*c64c658bSEtienne Carriere 	core_mmu_remove_mapping(MEM_AREA_IO_SEC, (void *)wdt->base, size);
277*c64c658bSEtienne Carriere err_remove_handler:
278*c64c658bSEtienne Carriere 	interrupt_remove_free_handler(it_hdlr);
279*c64c658bSEtienne Carriere err_free:
2805cbd8b3aSClément Léger 	free(wdt);
2815cbd8b3aSClément Léger 
2825cbd8b3aSClément Léger 	return TEE_ERROR_GENERIC;
2835cbd8b3aSClément Léger }
2845cbd8b3aSClément Léger 
2855cbd8b3aSClément Léger static const struct dt_device_match atmel_wdt_match_table[] = {
2865cbd8b3aSClément Léger 	{ .compatible = "atmel,sama5d4-wdt" },
2875cbd8b3aSClément Léger 	{ }
2885cbd8b3aSClément Léger };
2895cbd8b3aSClément Léger 
2905cbd8b3aSClément Léger DEFINE_DT_DRIVER(atmel_wdt_dt_driver) = {
2915cbd8b3aSClément Léger 	.name = "atmel_wdt",
2925cbd8b3aSClément Léger 	.type = DT_DRIVER_NOTYPE,
2935cbd8b3aSClément Léger 	.match_table = atmel_wdt_match_table,
2945cbd8b3aSClément Léger 	.probe = wdt_node_probe,
2955cbd8b3aSClément Léger };
296