| 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/core/irq.h" |
| 23 | #include "hw/core/registerfields.h" |
| 24 | #include "hw/misc/sifive_e_aon.h" |
| 25 | #include "qapi/visitor.h" |
| 26 | #include "qapi/error.h" |
| 27 | #include "system/watchdog.h" |
| 28 | #include "hw/core/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_LITTLE_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 | timer_init_ns(&r->wdog_timer, 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 void sifive_e_aon_finalize(Object *obj) |
| 293 | { |
| 294 | SiFiveEAONState *r = SIFIVE_E_AON(obj); |
| 295 | |
| 296 | timer_del(&r->wdog_timer); |
| 297 | } |
| 298 | |
| 299 | static const Property sifive_e_aon_properties[] = { |
| 300 | DEFINE_PROP_UINT64("wdogclk-frequency", SiFiveEAONState, wdogclk_freq, |
| 301 | SIFIVE_E_LFCLK_DEFAULT_FREQ), |
| 302 | }; |
| 303 | |
| 304 | static void sifive_e_aon_class_init(ObjectClass *oc, const void *data) |
| 305 | { |
| 306 | DeviceClass *dc = DEVICE_CLASS(oc); |
| 307 | |
| 308 | device_class_set_legacy_reset(dc, sifive_e_aon_reset); |
| 309 | device_class_set_props(dc, sifive_e_aon_properties); |
| 310 | } |
| 311 | |
| 312 | static const TypeInfo sifive_e_aon_info = { |
| 313 | .name = TYPE_SIFIVE_E_AON, |
| 314 | .parent = TYPE_SYS_BUS_DEVICE, |
| 315 | .instance_size = sizeof(SiFiveEAONState), |
| 316 | .instance_init = sifive_e_aon_init, |
| 317 | .instance_finalize = sifive_e_aon_finalize, |
| 318 | .class_init = sifive_e_aon_class_init, |
| 319 | }; |
| 320 | |
| 321 | static void sifive_e_aon_register_types(void) |
| 322 | { |
| 323 | type_register_static(&sifive_e_aon_info); |
| 324 | } |
| 325 | |
| 326 | type_init(sifive_e_aon_register_types) |