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