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