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