xref: /openbmc/qemu/hw/misc/sifive_e_aon.c (revision 28ae3179fc52d2e4d870b635c4a412aab99759e7)
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  
sifive_e_aon_wdt_update_wdogcount(SiFiveEAONState * r)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  
sifive_e_aon_wdt_update_state(SiFiveEAONState * r)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   */
sifive_e_aon_wdt_expired_cb(void * opaque)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
sifive_e_aon_wdt_read(void * opaque,hwaddr addr,unsigned int size)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
sifive_e_aon_wdt_write(void * opaque,hwaddr addr,uint64_t val64,unsigned int size)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
sifive_e_aon_read(void * opaque,hwaddr addr,unsigned int size)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
sifive_e_aon_write(void * opaque,hwaddr addr,uint64_t val64,unsigned int size)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  
sifive_e_aon_reset(DeviceState * dev)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  
sifive_e_aon_init(Object * obj)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  
sifive_e_aon_class_init(ObjectClass * oc,void * data)298  static void sifive_e_aon_class_init(ObjectClass *oc, void *data)
299  {
300      DeviceClass *dc = DEVICE_CLASS(oc);
301  
302      device_class_set_legacy_reset(dc, 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  
sifive_e_aon_register_types(void)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