1bf01a04fSTommy Wu /* 2bf01a04fSTommy Wu * SiFive HiFive1 AON (Always On Domain) for QEMU. 3bf01a04fSTommy Wu * 4bf01a04fSTommy Wu * Copyright (c) 2022 SiFive, Inc. All rights reserved. 5bf01a04fSTommy Wu * 6bf01a04fSTommy Wu * This program is free software; you can redistribute it and/or modify it 7bf01a04fSTommy Wu * under the terms and conditions of the GNU General Public License, 8bf01a04fSTommy Wu * version 2 or later, as published by the Free Software Foundation. 9bf01a04fSTommy Wu * 10bf01a04fSTommy Wu * This program is distributed in the hope it will be useful, but WITHOUT 11bf01a04fSTommy Wu * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 12bf01a04fSTommy Wu * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 13bf01a04fSTommy Wu * more details. 14bf01a04fSTommy Wu * 15bf01a04fSTommy Wu * You should have received a copy of the GNU General Public License along with 16bf01a04fSTommy Wu * this program. If not, see <http://www.gnu.org/licenses/>. 17bf01a04fSTommy Wu */ 18bf01a04fSTommy Wu 19bf01a04fSTommy Wu #include "qemu/osdep.h" 20bf01a04fSTommy Wu #include "qemu/timer.h" 21bf01a04fSTommy Wu #include "qemu/log.h" 22bf01a04fSTommy Wu #include "hw/irq.h" 23bf01a04fSTommy Wu #include "hw/registerfields.h" 24bf01a04fSTommy Wu #include "hw/misc/sifive_e_aon.h" 25bf01a04fSTommy Wu #include "qapi/visitor.h" 26bf01a04fSTommy Wu #include "qapi/error.h" 2732cad1ffSPhilippe Mathieu-Daudé #include "system/watchdog.h" 28bf01a04fSTommy Wu #include "hw/qdev-properties.h" 29bf01a04fSTommy Wu 30bf01a04fSTommy Wu REG32(AON_WDT_WDOGCFG, 0x0) 31bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, SCALE, 0, 4) 32bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, RSVD0, 4, 4) 33bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, RSTEN, 8, 1) 34bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, ZEROCMP, 9, 1) 35bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, RSVD1, 10, 2) 36bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, EN_ALWAYS, 12, 1) 37bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, EN_CORE_AWAKE, 13, 1) 38bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, RSVD2, 14, 14) 39bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, IP0, 28, 1) 40bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCFG, RSVD3, 29, 3) 41bf01a04fSTommy Wu REG32(AON_WDT_WDOGCOUNT, 0x8) 42bf01a04fSTommy Wu FIELD(AON_WDT_WDOGCOUNT, VALUE, 0, 31) 43bf01a04fSTommy Wu REG32(AON_WDT_WDOGS, 0x10) 44bf01a04fSTommy Wu REG32(AON_WDT_WDOGFEED, 0x18) 45bf01a04fSTommy Wu REG32(AON_WDT_WDOGKEY, 0x1c) 46bf01a04fSTommy Wu REG32(AON_WDT_WDOGCMP0, 0x20) 47bf01a04fSTommy Wu 48bf01a04fSTommy Wu static void sifive_e_aon_wdt_update_wdogcount(SiFiveEAONState *r) 49bf01a04fSTommy Wu { 50bf01a04fSTommy Wu int64_t now; 51bf01a04fSTommy Wu if (FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_ALWAYS) == 0 && 52bf01a04fSTommy Wu FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_CORE_AWAKE) == 0) { 53bf01a04fSTommy Wu return; 54bf01a04fSTommy Wu } 55bf01a04fSTommy Wu 56bf01a04fSTommy Wu now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 57bf01a04fSTommy Wu r->wdogcount += muldiv64(now - r->wdog_restart_time, 58bf01a04fSTommy Wu r->wdogclk_freq, NANOSECONDS_PER_SECOND); 59bf01a04fSTommy Wu 60bf01a04fSTommy Wu /* Clean the most significant bit. */ 61bf01a04fSTommy Wu r->wdogcount &= R_AON_WDT_WDOGCOUNT_VALUE_MASK; 62bf01a04fSTommy Wu r->wdog_restart_time = now; 63bf01a04fSTommy Wu } 64bf01a04fSTommy Wu 65bf01a04fSTommy Wu static void sifive_e_aon_wdt_update_state(SiFiveEAONState *r) 66bf01a04fSTommy Wu { 67bf01a04fSTommy Wu uint16_t wdogs; 68bf01a04fSTommy Wu bool cmp_signal = false; 69bf01a04fSTommy Wu sifive_e_aon_wdt_update_wdogcount(r); 70bf01a04fSTommy Wu wdogs = (uint16_t)(r->wdogcount >> 71bf01a04fSTommy Wu FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, SCALE)); 72bf01a04fSTommy Wu 73bf01a04fSTommy Wu if (wdogs >= r->wdogcmp0) { 74bf01a04fSTommy Wu cmp_signal = true; 75bf01a04fSTommy Wu if (FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, ZEROCMP) == 1) { 76bf01a04fSTommy Wu r->wdogcount = 0; 77bf01a04fSTommy Wu wdogs = 0; 78bf01a04fSTommy Wu } 79bf01a04fSTommy Wu } 80bf01a04fSTommy Wu 81bf01a04fSTommy Wu if (cmp_signal) { 82bf01a04fSTommy Wu if (FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, RSTEN) == 1) { 83bf01a04fSTommy Wu watchdog_perform_action(); 84bf01a04fSTommy Wu } 85bf01a04fSTommy Wu r->wdogcfg = FIELD_DP32(r->wdogcfg, AON_WDT_WDOGCFG, IP0, 1); 86bf01a04fSTommy Wu } 87bf01a04fSTommy Wu 88bf01a04fSTommy Wu qemu_set_irq(r->wdog_irq, FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, IP0)); 89bf01a04fSTommy Wu 90bf01a04fSTommy Wu if (wdogs < r->wdogcmp0 && 91bf01a04fSTommy Wu (FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_ALWAYS) == 1 || 92bf01a04fSTommy Wu FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_CORE_AWAKE) == 1)) { 93bf01a04fSTommy Wu int64_t next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 94bf01a04fSTommy Wu next += muldiv64((r->wdogcmp0 - wdogs) << 95bf01a04fSTommy Wu FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, SCALE), 96bf01a04fSTommy Wu NANOSECONDS_PER_SECOND, r->wdogclk_freq); 97bf01a04fSTommy Wu timer_mod(r->wdog_timer, next); 98bf01a04fSTommy Wu } else { 99bf01a04fSTommy Wu timer_mod(r->wdog_timer, INT64_MAX); 100bf01a04fSTommy Wu } 101bf01a04fSTommy Wu } 102bf01a04fSTommy Wu 103bf01a04fSTommy Wu /* 104bf01a04fSTommy Wu * Callback used when the timer set using timer_mod expires. 105bf01a04fSTommy Wu */ 106bf01a04fSTommy Wu static void sifive_e_aon_wdt_expired_cb(void *opaque) 107bf01a04fSTommy Wu { 108bf01a04fSTommy Wu SiFiveEAONState *r = SIFIVE_E_AON(opaque); 109bf01a04fSTommy Wu sifive_e_aon_wdt_update_state(r); 110bf01a04fSTommy Wu } 111bf01a04fSTommy Wu 112bf01a04fSTommy Wu static uint64_t 113bf01a04fSTommy Wu sifive_e_aon_wdt_read(void *opaque, hwaddr addr, unsigned int size) 114bf01a04fSTommy Wu { 115bf01a04fSTommy Wu SiFiveEAONState *r = SIFIVE_E_AON(opaque); 116bf01a04fSTommy Wu 117bf01a04fSTommy Wu switch (addr) { 118bf01a04fSTommy Wu case A_AON_WDT_WDOGCFG: 119bf01a04fSTommy Wu return r->wdogcfg; 120bf01a04fSTommy Wu case A_AON_WDT_WDOGCOUNT: 121bf01a04fSTommy Wu sifive_e_aon_wdt_update_wdogcount(r); 122bf01a04fSTommy Wu return r->wdogcount; 123bf01a04fSTommy Wu case A_AON_WDT_WDOGS: 124bf01a04fSTommy Wu sifive_e_aon_wdt_update_wdogcount(r); 125bf01a04fSTommy Wu return r->wdogcount >> 126bf01a04fSTommy Wu FIELD_EX32(r->wdogcfg, 127bf01a04fSTommy Wu AON_WDT_WDOGCFG, 128bf01a04fSTommy Wu SCALE); 129bf01a04fSTommy Wu case A_AON_WDT_WDOGFEED: 130bf01a04fSTommy Wu return 0; 131bf01a04fSTommy Wu case A_AON_WDT_WDOGKEY: 132bf01a04fSTommy Wu return r->wdogunlock; 133bf01a04fSTommy Wu case A_AON_WDT_WDOGCMP0: 134bf01a04fSTommy Wu return r->wdogcmp0; 135bf01a04fSTommy Wu default: 136bf01a04fSTommy Wu qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read: addr=0x%x\n", 137bf01a04fSTommy Wu __func__, (int)addr); 138bf01a04fSTommy Wu } 139bf01a04fSTommy Wu 140bf01a04fSTommy Wu return 0; 141bf01a04fSTommy Wu } 142bf01a04fSTommy Wu 143bf01a04fSTommy Wu static void 144bf01a04fSTommy Wu sifive_e_aon_wdt_write(void *opaque, hwaddr addr, 145bf01a04fSTommy Wu uint64_t val64, unsigned int size) 146bf01a04fSTommy Wu { 147bf01a04fSTommy Wu SiFiveEAONState *r = SIFIVE_E_AON(opaque); 148bf01a04fSTommy Wu uint32_t value = val64; 149bf01a04fSTommy Wu 150bf01a04fSTommy Wu switch (addr) { 151bf01a04fSTommy Wu case A_AON_WDT_WDOGCFG: { 152bf01a04fSTommy Wu uint8_t new_en_always; 153bf01a04fSTommy Wu uint8_t new_en_core_awake; 154bf01a04fSTommy Wu uint8_t old_en_always; 155bf01a04fSTommy Wu uint8_t old_en_core_awake; 156bf01a04fSTommy Wu if (r->wdogunlock == 0) { 157bf01a04fSTommy Wu return; 158bf01a04fSTommy Wu } 159bf01a04fSTommy Wu 160bf01a04fSTommy Wu new_en_always = FIELD_EX32(value, AON_WDT_WDOGCFG, EN_ALWAYS); 161bf01a04fSTommy Wu new_en_core_awake = FIELD_EX32(value, AON_WDT_WDOGCFG, EN_CORE_AWAKE); 162bf01a04fSTommy Wu old_en_always = FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_ALWAYS); 163bf01a04fSTommy Wu old_en_core_awake = FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, 164bf01a04fSTommy Wu EN_CORE_AWAKE); 165bf01a04fSTommy Wu 166bf01a04fSTommy Wu if ((old_en_always || 167bf01a04fSTommy Wu old_en_core_awake) == 1 && 168bf01a04fSTommy Wu (new_en_always || 169bf01a04fSTommy Wu new_en_core_awake) == 0) { 170bf01a04fSTommy Wu sifive_e_aon_wdt_update_wdogcount(r); 171bf01a04fSTommy Wu } else if ((old_en_always || 172bf01a04fSTommy Wu old_en_core_awake) == 0 && 173bf01a04fSTommy Wu (new_en_always || 174bf01a04fSTommy Wu new_en_core_awake) == 1) { 175bf01a04fSTommy Wu r->wdog_restart_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 176bf01a04fSTommy Wu } 177bf01a04fSTommy Wu r->wdogcfg = value; 178bf01a04fSTommy Wu r->wdogunlock = 0; 179bf01a04fSTommy Wu break; 180bf01a04fSTommy Wu } 181bf01a04fSTommy Wu case A_AON_WDT_WDOGCOUNT: 182bf01a04fSTommy Wu if (r->wdogunlock == 0) { 183bf01a04fSTommy Wu return; 184bf01a04fSTommy Wu } 185bf01a04fSTommy Wu r->wdogcount = value & R_AON_WDT_WDOGCOUNT_VALUE_MASK; 186bf01a04fSTommy Wu r->wdog_restart_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 187bf01a04fSTommy Wu r->wdogunlock = 0; 188bf01a04fSTommy Wu break; 189bf01a04fSTommy Wu case A_AON_WDT_WDOGS: 190bf01a04fSTommy Wu return; 191bf01a04fSTommy Wu case A_AON_WDT_WDOGFEED: 192bf01a04fSTommy Wu if (r->wdogunlock == 0) { 193bf01a04fSTommy Wu return; 194bf01a04fSTommy Wu } 195bf01a04fSTommy Wu if (value == SIFIVE_E_AON_WDOGFEED) { 196bf01a04fSTommy Wu r->wdogcount = 0; 197bf01a04fSTommy Wu r->wdog_restart_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); 198bf01a04fSTommy Wu } 199bf01a04fSTommy Wu r->wdogunlock = 0; 200bf01a04fSTommy Wu break; 201bf01a04fSTommy Wu case A_AON_WDT_WDOGKEY: 202bf01a04fSTommy Wu if (value == SIFIVE_E_AON_WDOGKEY) { 203bf01a04fSTommy Wu r->wdogunlock = 1; 204bf01a04fSTommy Wu } 205bf01a04fSTommy Wu break; 206bf01a04fSTommy Wu case A_AON_WDT_WDOGCMP0: 207bf01a04fSTommy Wu if (r->wdogunlock == 0) { 208bf01a04fSTommy Wu return; 209bf01a04fSTommy Wu } 210bf01a04fSTommy Wu r->wdogcmp0 = (uint16_t) value; 211bf01a04fSTommy Wu r->wdogunlock = 0; 212bf01a04fSTommy Wu break; 213bf01a04fSTommy Wu default: 214bf01a04fSTommy Wu qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%x v=0x%x\n", 215bf01a04fSTommy Wu __func__, (int)addr, (int)value); 216bf01a04fSTommy Wu } 217bf01a04fSTommy Wu sifive_e_aon_wdt_update_state(r); 218bf01a04fSTommy Wu } 219bf01a04fSTommy Wu 220bf01a04fSTommy Wu static uint64_t 221bf01a04fSTommy Wu sifive_e_aon_read(void *opaque, hwaddr addr, unsigned int size) 222bf01a04fSTommy Wu { 223bf01a04fSTommy Wu if (addr < SIFIVE_E_AON_RTC) { 224bf01a04fSTommy Wu return sifive_e_aon_wdt_read(opaque, addr, size); 225bf01a04fSTommy Wu } else if (addr < SIFIVE_E_AON_MAX) { 226bf01a04fSTommy Wu qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read: addr=0x%x\n", 227bf01a04fSTommy Wu __func__, (int)addr); 228bf01a04fSTommy Wu } else { 229bf01a04fSTommy Wu qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read: addr=0x%x\n", 230bf01a04fSTommy Wu __func__, (int)addr); 231bf01a04fSTommy Wu } 232bf01a04fSTommy Wu return 0; 233bf01a04fSTommy Wu } 234bf01a04fSTommy Wu 235bf01a04fSTommy Wu static void 236bf01a04fSTommy Wu sifive_e_aon_write(void *opaque, hwaddr addr, 237bf01a04fSTommy Wu uint64_t val64, unsigned int size) 238bf01a04fSTommy Wu { 239bf01a04fSTommy Wu if (addr < SIFIVE_E_AON_RTC) { 240bf01a04fSTommy Wu sifive_e_aon_wdt_write(opaque, addr, val64, size); 241bf01a04fSTommy Wu } else if (addr < SIFIVE_E_AON_MAX) { 242bf01a04fSTommy Wu qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write: addr=0x%x\n", 243bf01a04fSTommy Wu __func__, (int)addr); 244bf01a04fSTommy Wu } else { 245bf01a04fSTommy Wu qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%x\n", 246bf01a04fSTommy Wu __func__, (int)addr); 247bf01a04fSTommy Wu } 248bf01a04fSTommy Wu } 249bf01a04fSTommy Wu 250bf01a04fSTommy Wu static const MemoryRegionOps sifive_e_aon_ops = { 251bf01a04fSTommy Wu .read = sifive_e_aon_read, 252bf01a04fSTommy Wu .write = sifive_e_aon_write, 253bf01a04fSTommy Wu .endianness = DEVICE_NATIVE_ENDIAN, 254bf01a04fSTommy Wu .impl = { 255bf01a04fSTommy Wu .min_access_size = 4, 256bf01a04fSTommy Wu .max_access_size = 4 257bf01a04fSTommy Wu }, 258bf01a04fSTommy Wu .valid = { 259bf01a04fSTommy Wu .min_access_size = 4, 260bf01a04fSTommy Wu .max_access_size = 4 261bf01a04fSTommy Wu } 262bf01a04fSTommy Wu }; 263bf01a04fSTommy Wu 264bf01a04fSTommy Wu static void sifive_e_aon_reset(DeviceState *dev) 265bf01a04fSTommy Wu { 266bf01a04fSTommy Wu SiFiveEAONState *r = SIFIVE_E_AON(dev); 267bf01a04fSTommy Wu 268bf01a04fSTommy Wu r->wdogcfg = FIELD_DP32(r->wdogcfg, AON_WDT_WDOGCFG, RSTEN, 0); 269bf01a04fSTommy Wu r->wdogcfg = FIELD_DP32(r->wdogcfg, AON_WDT_WDOGCFG, EN_ALWAYS, 0); 270bf01a04fSTommy Wu r->wdogcfg = FIELD_DP32(r->wdogcfg, AON_WDT_WDOGCFG, EN_CORE_AWAKE, 0); 271bf01a04fSTommy Wu r->wdogcmp0 = 0xbeef; 272bf01a04fSTommy Wu 273bf01a04fSTommy Wu sifive_e_aon_wdt_update_state(r); 274bf01a04fSTommy Wu } 275bf01a04fSTommy Wu 276bf01a04fSTommy Wu static void sifive_e_aon_init(Object *obj) 277bf01a04fSTommy Wu { 278bf01a04fSTommy Wu SysBusDevice *sbd = SYS_BUS_DEVICE(obj); 279bf01a04fSTommy Wu SiFiveEAONState *r = SIFIVE_E_AON(obj); 280bf01a04fSTommy Wu 281bf01a04fSTommy Wu memory_region_init_io(&r->mmio, OBJECT(r), &sifive_e_aon_ops, r, 282bf01a04fSTommy Wu TYPE_SIFIVE_E_AON, SIFIVE_E_AON_MAX); 283bf01a04fSTommy Wu sysbus_init_mmio(sbd, &r->mmio); 284bf01a04fSTommy Wu 285bf01a04fSTommy Wu /* watchdog timer */ 286bf01a04fSTommy Wu r->wdog_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, 287bf01a04fSTommy Wu sifive_e_aon_wdt_expired_cb, r); 288bf01a04fSTommy Wu r->wdogclk_freq = SIFIVE_E_LFCLK_DEFAULT_FREQ; 289bf01a04fSTommy Wu sysbus_init_irq(sbd, &r->wdog_irq); 290bf01a04fSTommy Wu } 291bf01a04fSTommy Wu 29230029973SRichard Henderson static const Property sifive_e_aon_properties[] = { 293bf01a04fSTommy Wu DEFINE_PROP_UINT64("wdogclk-frequency", SiFiveEAONState, wdogclk_freq, 294bf01a04fSTommy Wu SIFIVE_E_LFCLK_DEFAULT_FREQ), 295bf01a04fSTommy Wu }; 296bf01a04fSTommy Wu 297*12d1a768SPhilippe Mathieu-Daudé static void sifive_e_aon_class_init(ObjectClass *oc, const void *data) 298bf01a04fSTommy Wu { 299bf01a04fSTommy Wu DeviceClass *dc = DEVICE_CLASS(oc); 300bf01a04fSTommy Wu 301e3d08143SPeter Maydell device_class_set_legacy_reset(dc, sifive_e_aon_reset); 302bf01a04fSTommy Wu device_class_set_props(dc, sifive_e_aon_properties); 303bf01a04fSTommy Wu } 304bf01a04fSTommy Wu 305bf01a04fSTommy Wu static const TypeInfo sifive_e_aon_info = { 306bf01a04fSTommy Wu .name = TYPE_SIFIVE_E_AON, 307bf01a04fSTommy Wu .parent = TYPE_SYS_BUS_DEVICE, 308bf01a04fSTommy Wu .instance_size = sizeof(SiFiveEAONState), 309bf01a04fSTommy Wu .instance_init = sifive_e_aon_init, 310bf01a04fSTommy Wu .class_init = sifive_e_aon_class_init, 311bf01a04fSTommy Wu }; 312bf01a04fSTommy Wu 313bf01a04fSTommy Wu static void sifive_e_aon_register_types(void) 314bf01a04fSTommy Wu { 315bf01a04fSTommy Wu type_register_static(&sifive_e_aon_info); 316bf01a04fSTommy Wu } 317bf01a04fSTommy Wu 318bf01a04fSTommy Wu type_init(sifive_e_aon_register_types) 319