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