Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
hw/misc: sifive_e_aon: Support the watchdog timer of HiFive 1 rev b.
The watchdog timer is in the always-on domain device of HiFive 1 rev b, so this patch added the AON device to the sifive_e machine. This patch only implemented the functionality of the watchdog timer. Signed-off-by: Tommy Wu <tommy.wu@sifive.com> Reviewed-by: Frank Chang <frank.chang@sifive.com> Acked-by: Alistair Francis <alistair.francis@wdc.com> Message-Id: <20230627141216.3962299-2-tommy.wu@sifive.com> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
- Loading branch information
1 parent
11b937b
commit bf01a04
Showing
4 changed files
with
383 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -158,6 +158,9 @@ config SIFIVE_TEST | |
| config SIFIVE_E_PRCI | ||
| bool | ||
|
|
||
| config SIFIVE_E_AON | ||
| bool | ||
|
|
||
| config SIFIVE_U_OTP | ||
| bool | ||
|
|
||
|
|
||
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,319 @@ | ||
| /* | ||
| * SiFive HiFive1 AON (Always On Domain) for QEMU. | ||
| * | ||
| * Copyright (c) 2022 SiFive, Inc. All rights reserved. | ||
| * | ||
| * This program is free software; you can redistribute it and/or modify it | ||
| * under the terms and conditions of the GNU General Public License, | ||
| * version 2 or later, as published by the Free Software Foundation. | ||
| * | ||
| * This program is distributed in the hope it will be useful, but WITHOUT | ||
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| * more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License along with | ||
| * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| #include "qemu/osdep.h" | ||
| #include "qemu/timer.h" | ||
| #include "qemu/log.h" | ||
| #include "hw/irq.h" | ||
| #include "hw/registerfields.h" | ||
| #include "hw/misc/sifive_e_aon.h" | ||
| #include "qapi/visitor.h" | ||
| #include "qapi/error.h" | ||
| #include "sysemu/watchdog.h" | ||
| #include "hw/qdev-properties.h" | ||
|
|
||
| REG32(AON_WDT_WDOGCFG, 0x0) | ||
| FIELD(AON_WDT_WDOGCFG, SCALE, 0, 4) | ||
| FIELD(AON_WDT_WDOGCFG, RSVD0, 4, 4) | ||
| FIELD(AON_WDT_WDOGCFG, RSTEN, 8, 1) | ||
| FIELD(AON_WDT_WDOGCFG, ZEROCMP, 9, 1) | ||
| FIELD(AON_WDT_WDOGCFG, RSVD1, 10, 2) | ||
| FIELD(AON_WDT_WDOGCFG, EN_ALWAYS, 12, 1) | ||
| FIELD(AON_WDT_WDOGCFG, EN_CORE_AWAKE, 13, 1) | ||
| FIELD(AON_WDT_WDOGCFG, RSVD2, 14, 14) | ||
| FIELD(AON_WDT_WDOGCFG, IP0, 28, 1) | ||
| FIELD(AON_WDT_WDOGCFG, RSVD3, 29, 3) | ||
| REG32(AON_WDT_WDOGCOUNT, 0x8) | ||
| FIELD(AON_WDT_WDOGCOUNT, VALUE, 0, 31) | ||
| REG32(AON_WDT_WDOGS, 0x10) | ||
| REG32(AON_WDT_WDOGFEED, 0x18) | ||
| REG32(AON_WDT_WDOGKEY, 0x1c) | ||
| REG32(AON_WDT_WDOGCMP0, 0x20) | ||
|
|
||
| static void sifive_e_aon_wdt_update_wdogcount(SiFiveEAONState *r) | ||
| { | ||
| int64_t now; | ||
| if (FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_ALWAYS) == 0 && | ||
| FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_CORE_AWAKE) == 0) { | ||
| return; | ||
| } | ||
|
|
||
| now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | ||
| r->wdogcount += muldiv64(now - r->wdog_restart_time, | ||
| r->wdogclk_freq, NANOSECONDS_PER_SECOND); | ||
|
|
||
| /* Clean the most significant bit. */ | ||
| r->wdogcount &= R_AON_WDT_WDOGCOUNT_VALUE_MASK; | ||
| r->wdog_restart_time = now; | ||
| } | ||
|
|
||
| static void sifive_e_aon_wdt_update_state(SiFiveEAONState *r) | ||
| { | ||
| uint16_t wdogs; | ||
| bool cmp_signal = false; | ||
| sifive_e_aon_wdt_update_wdogcount(r); | ||
| wdogs = (uint16_t)(r->wdogcount >> | ||
| FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, SCALE)); | ||
|
|
||
| if (wdogs >= r->wdogcmp0) { | ||
| cmp_signal = true; | ||
| if (FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, ZEROCMP) == 1) { | ||
| r->wdogcount = 0; | ||
| wdogs = 0; | ||
| } | ||
| } | ||
|
|
||
| if (cmp_signal) { | ||
| if (FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, RSTEN) == 1) { | ||
| watchdog_perform_action(); | ||
| } | ||
| r->wdogcfg = FIELD_DP32(r->wdogcfg, AON_WDT_WDOGCFG, IP0, 1); | ||
| } | ||
|
|
||
| qemu_set_irq(r->wdog_irq, FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, IP0)); | ||
|
|
||
| if (wdogs < r->wdogcmp0 && | ||
| (FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_ALWAYS) == 1 || | ||
| FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_CORE_AWAKE) == 1)) { | ||
| int64_t next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | ||
| next += muldiv64((r->wdogcmp0 - wdogs) << | ||
| FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, SCALE), | ||
| NANOSECONDS_PER_SECOND, r->wdogclk_freq); | ||
| timer_mod(r->wdog_timer, next); | ||
| } else { | ||
| timer_mod(r->wdog_timer, INT64_MAX); | ||
| } | ||
| } | ||
|
|
||
| /* | ||
| * Callback used when the timer set using timer_mod expires. | ||
| */ | ||
| static void sifive_e_aon_wdt_expired_cb(void *opaque) | ||
| { | ||
| SiFiveEAONState *r = SIFIVE_E_AON(opaque); | ||
| sifive_e_aon_wdt_update_state(r); | ||
| } | ||
|
|
||
| static uint64_t | ||
| sifive_e_aon_wdt_read(void *opaque, hwaddr addr, unsigned int size) | ||
| { | ||
| SiFiveEAONState *r = SIFIVE_E_AON(opaque); | ||
|
|
||
| switch (addr) { | ||
| case A_AON_WDT_WDOGCFG: | ||
| return r->wdogcfg; | ||
| case A_AON_WDT_WDOGCOUNT: | ||
| sifive_e_aon_wdt_update_wdogcount(r); | ||
| return r->wdogcount; | ||
| case A_AON_WDT_WDOGS: | ||
| sifive_e_aon_wdt_update_wdogcount(r); | ||
| return r->wdogcount >> | ||
| FIELD_EX32(r->wdogcfg, | ||
| AON_WDT_WDOGCFG, | ||
| SCALE); | ||
| case A_AON_WDT_WDOGFEED: | ||
| return 0; | ||
| case A_AON_WDT_WDOGKEY: | ||
| return r->wdogunlock; | ||
| case A_AON_WDT_WDOGCMP0: | ||
| return r->wdogcmp0; | ||
| default: | ||
| qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read: addr=0x%x\n", | ||
| __func__, (int)addr); | ||
| } | ||
|
|
||
| return 0; | ||
| } | ||
|
|
||
| static void | ||
| sifive_e_aon_wdt_write(void *opaque, hwaddr addr, | ||
| uint64_t val64, unsigned int size) | ||
| { | ||
| SiFiveEAONState *r = SIFIVE_E_AON(opaque); | ||
| uint32_t value = val64; | ||
|
|
||
| switch (addr) { | ||
| case A_AON_WDT_WDOGCFG: { | ||
| uint8_t new_en_always; | ||
| uint8_t new_en_core_awake; | ||
| uint8_t old_en_always; | ||
| uint8_t old_en_core_awake; | ||
| if (r->wdogunlock == 0) { | ||
| return; | ||
| } | ||
|
|
||
| new_en_always = FIELD_EX32(value, AON_WDT_WDOGCFG, EN_ALWAYS); | ||
| new_en_core_awake = FIELD_EX32(value, AON_WDT_WDOGCFG, EN_CORE_AWAKE); | ||
| old_en_always = FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, EN_ALWAYS); | ||
| old_en_core_awake = FIELD_EX32(r->wdogcfg, AON_WDT_WDOGCFG, | ||
| EN_CORE_AWAKE); | ||
|
|
||
| if ((old_en_always || | ||
| old_en_core_awake) == 1 && | ||
| (new_en_always || | ||
| new_en_core_awake) == 0) { | ||
| sifive_e_aon_wdt_update_wdogcount(r); | ||
| } else if ((old_en_always || | ||
| old_en_core_awake) == 0 && | ||
| (new_en_always || | ||
| new_en_core_awake) == 1) { | ||
| r->wdog_restart_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | ||
| } | ||
| r->wdogcfg = value; | ||
| r->wdogunlock = 0; | ||
| break; | ||
| } | ||
| case A_AON_WDT_WDOGCOUNT: | ||
| if (r->wdogunlock == 0) { | ||
| return; | ||
| } | ||
| r->wdogcount = value & R_AON_WDT_WDOGCOUNT_VALUE_MASK; | ||
| r->wdog_restart_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | ||
| r->wdogunlock = 0; | ||
| break; | ||
| case A_AON_WDT_WDOGS: | ||
| return; | ||
| case A_AON_WDT_WDOGFEED: | ||
| if (r->wdogunlock == 0) { | ||
| return; | ||
| } | ||
| if (value == SIFIVE_E_AON_WDOGFEED) { | ||
| r->wdogcount = 0; | ||
| r->wdog_restart_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | ||
| } | ||
| r->wdogunlock = 0; | ||
| break; | ||
| case A_AON_WDT_WDOGKEY: | ||
| if (value == SIFIVE_E_AON_WDOGKEY) { | ||
| r->wdogunlock = 1; | ||
| } | ||
| break; | ||
| case A_AON_WDT_WDOGCMP0: | ||
| if (r->wdogunlock == 0) { | ||
| return; | ||
| } | ||
| r->wdogcmp0 = (uint16_t) value; | ||
| r->wdogunlock = 0; | ||
| break; | ||
| default: | ||
| qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%x v=0x%x\n", | ||
| __func__, (int)addr, (int)value); | ||
| } | ||
| sifive_e_aon_wdt_update_state(r); | ||
| } | ||
|
|
||
| static uint64_t | ||
| sifive_e_aon_read(void *opaque, hwaddr addr, unsigned int size) | ||
| { | ||
| if (addr < SIFIVE_E_AON_RTC) { | ||
| return sifive_e_aon_wdt_read(opaque, addr, size); | ||
| } else if (addr < SIFIVE_E_AON_MAX) { | ||
| qemu_log_mask(LOG_UNIMP, "%s: Unimplemented read: addr=0x%x\n", | ||
| __func__, (int)addr); | ||
| } else { | ||
| qemu_log_mask(LOG_GUEST_ERROR, "%s: bad read: addr=0x%x\n", | ||
| __func__, (int)addr); | ||
| } | ||
| return 0; | ||
| } | ||
|
|
||
| static void | ||
| sifive_e_aon_write(void *opaque, hwaddr addr, | ||
| uint64_t val64, unsigned int size) | ||
| { | ||
| if (addr < SIFIVE_E_AON_RTC) { | ||
| sifive_e_aon_wdt_write(opaque, addr, val64, size); | ||
| } else if (addr < SIFIVE_E_AON_MAX) { | ||
| qemu_log_mask(LOG_UNIMP, "%s: Unimplemented write: addr=0x%x\n", | ||
| __func__, (int)addr); | ||
| } else { | ||
| qemu_log_mask(LOG_GUEST_ERROR, "%s: bad write: addr=0x%x\n", | ||
| __func__, (int)addr); | ||
| } | ||
| } | ||
|
|
||
| static const MemoryRegionOps sifive_e_aon_ops = { | ||
| .read = sifive_e_aon_read, | ||
| .write = sifive_e_aon_write, | ||
| .endianness = DEVICE_NATIVE_ENDIAN, | ||
| .impl = { | ||
| .min_access_size = 4, | ||
| .max_access_size = 4 | ||
| }, | ||
| .valid = { | ||
| .min_access_size = 4, | ||
| .max_access_size = 4 | ||
| } | ||
| }; | ||
|
|
||
| static void sifive_e_aon_reset(DeviceState *dev) | ||
| { | ||
| SiFiveEAONState *r = SIFIVE_E_AON(dev); | ||
|
|
||
| r->wdogcfg = FIELD_DP32(r->wdogcfg, AON_WDT_WDOGCFG, RSTEN, 0); | ||
| r->wdogcfg = FIELD_DP32(r->wdogcfg, AON_WDT_WDOGCFG, EN_ALWAYS, 0); | ||
| r->wdogcfg = FIELD_DP32(r->wdogcfg, AON_WDT_WDOGCFG, EN_CORE_AWAKE, 0); | ||
| r->wdogcmp0 = 0xbeef; | ||
|
|
||
| sifive_e_aon_wdt_update_state(r); | ||
| } | ||
|
|
||
| static void sifive_e_aon_init(Object *obj) | ||
| { | ||
| SysBusDevice *sbd = SYS_BUS_DEVICE(obj); | ||
| SiFiveEAONState *r = SIFIVE_E_AON(obj); | ||
|
|
||
| memory_region_init_io(&r->mmio, OBJECT(r), &sifive_e_aon_ops, r, | ||
| TYPE_SIFIVE_E_AON, SIFIVE_E_AON_MAX); | ||
| sysbus_init_mmio(sbd, &r->mmio); | ||
|
|
||
| /* watchdog timer */ | ||
| r->wdog_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, | ||
| sifive_e_aon_wdt_expired_cb, r); | ||
| r->wdogclk_freq = SIFIVE_E_LFCLK_DEFAULT_FREQ; | ||
| sysbus_init_irq(sbd, &r->wdog_irq); | ||
| } | ||
|
|
||
| static Property sifive_e_aon_properties[] = { | ||
| DEFINE_PROP_UINT64("wdogclk-frequency", SiFiveEAONState, wdogclk_freq, | ||
| SIFIVE_E_LFCLK_DEFAULT_FREQ), | ||
| DEFINE_PROP_END_OF_LIST(), | ||
| }; | ||
|
|
||
| static void sifive_e_aon_class_init(ObjectClass *oc, void *data) | ||
| { | ||
| DeviceClass *dc = DEVICE_CLASS(oc); | ||
|
|
||
| dc->reset = sifive_e_aon_reset; | ||
| device_class_set_props(dc, sifive_e_aon_properties); | ||
| } | ||
|
|
||
| static const TypeInfo sifive_e_aon_info = { | ||
| .name = TYPE_SIFIVE_E_AON, | ||
| .parent = TYPE_SYS_BUS_DEVICE, | ||
| .instance_size = sizeof(SiFiveEAONState), | ||
| .instance_init = sifive_e_aon_init, | ||
| .class_init = sifive_e_aon_class_init, | ||
| }; | ||
|
|
||
| static void sifive_e_aon_register_types(void) | ||
| { | ||
| type_register_static(&sifive_e_aon_info); | ||
| } | ||
|
|
||
| type_init(sifive_e_aon_register_types) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| /* | ||
| * SiFive HiFive1 AON (Always On Domain) interface. | ||
| * | ||
| * Copyright (c) 2022 SiFive, Inc. All rights reserved. | ||
| * | ||
| * This program is free software; you can redistribute it and/or modify it | ||
| * under the terms and conditions of the GNU General Public License, | ||
| * version 2 or later, as published by the Free Software Foundation. | ||
| * | ||
| * This program is distributed in the hope it will be useful, but WITHOUT | ||
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | ||
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | ||
| * more details. | ||
| * | ||
| * You should have received a copy of the GNU General Public License along with | ||
| * this program. If not, see <http://www.gnu.org/licenses/>. | ||
| */ | ||
|
|
||
| #ifndef HW_SIFIVE_AON_H | ||
| #define HW_SIFIVE_AON_H | ||
|
|
||
| #include "hw/sysbus.h" | ||
| #include "qom/object.h" | ||
|
|
||
| #define TYPE_SIFIVE_E_AON "riscv.sifive.e.aon" | ||
| OBJECT_DECLARE_SIMPLE_TYPE(SiFiveEAONState, SIFIVE_E_AON) | ||
|
|
||
| #define SIFIVE_E_AON_WDOGKEY (0x51F15E) | ||
| #define SIFIVE_E_AON_WDOGFEED (0xD09F00D) | ||
| #define SIFIVE_E_LFCLK_DEFAULT_FREQ (32768) | ||
|
|
||
| enum { | ||
| SIFIVE_E_AON_WDT = 0x0, | ||
| SIFIVE_E_AON_RTC = 0x40, | ||
| SIFIVE_E_AON_LFROSC = 0x70, | ||
| SIFIVE_E_AON_BACKUP = 0x80, | ||
| SIFIVE_E_AON_PMU = 0x100, | ||
| SIFIVE_E_AON_MAX = 0x150 | ||
| }; | ||
|
|
||
| struct SiFiveEAONState { | ||
| /*< private >*/ | ||
| SysBusDevice parent_obj; | ||
|
|
||
| /*< public >*/ | ||
| MemoryRegion mmio; | ||
|
|
||
| /*< watchdog timer >*/ | ||
| QEMUTimer *wdog_timer; | ||
| qemu_irq wdog_irq; | ||
| uint64_t wdog_restart_time; | ||
| uint64_t wdogclk_freq; | ||
|
|
||
| uint32_t wdogcfg; | ||
| uint16_t wdogcmp0; | ||
| uint32_t wdogcount; | ||
| uint8_t wdogunlock; | ||
| }; | ||
|
|
||
| #endif |