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