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
sifive_e_aon_wdt_update_wdogcount(SiFiveEAONState * r)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
sifive_e_aon_wdt_update_state(SiFiveEAONState * r)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 */
sifive_e_aon_wdt_expired_cb(void * opaque)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
sifive_e_aon_wdt_read(void * opaque,hwaddr addr,unsigned int size)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
sifive_e_aon_wdt_write(void * opaque,hwaddr addr,uint64_t val64,unsigned int size)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
sifive_e_aon_read(void * opaque,hwaddr addr,unsigned int size)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
sifive_e_aon_write(void * opaque,hwaddr addr,uint64_t val64,unsigned int size)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
sifive_e_aon_reset(DeviceState * dev)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
sifive_e_aon_init(Object * obj)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
sifive_e_aon_class_init(ObjectClass * oc,const void * data)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
sifive_e_aon_register_types(void)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