1 /*
2  * ARM CMSDK APB watchdog emulation
3  *
4  * Copyright (c) 2018 Linaro Limited
5  * Written by Peter Maydell
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 or
9  *  (at your option) any later version.
10  */
11 
12 /*
13  * This is a model of the "APB watchdog" which is part of the Cortex-M
14  * System Design Kit (CMSDK) and documented in the Cortex-M System
15  * Design Kit Technical Reference Manual (ARM DDI0479C):
16  * https://developer.arm.com/products/system-design/system-design-kits/cortex-m-system-design-kit
17  *
18  * We also support the variant of this device found in the TI
19  * Stellaris/Luminary boards and documented in:
20  * http://www.ti.com/lit/ds/symlink/lm3s6965.pdf
21  */
22 
23 #include "qemu/osdep.h"
24 #include "qemu/log.h"
25 #include "trace.h"
26 #include "qapi/error.h"
27 #include "qemu/main-loop.h"
28 #include "qemu/module.h"
29 #include "sysemu/watchdog.h"
30 #include "hw/sysbus.h"
31 #include "hw/registerfields.h"
32 #include "hw/watchdog/cmsdk-apb-watchdog.h"
33 
34 REG32(WDOGLOAD, 0x0)
35 REG32(WDOGVALUE, 0x4)
36 REG32(WDOGCONTROL, 0x8)
37     FIELD(WDOGCONTROL, INTEN, 0, 1)
38     FIELD(WDOGCONTROL, RESEN, 1, 1)
39 #define R_WDOGCONTROL_VALID_MASK (R_WDOGCONTROL_INTEN_MASK | \
40                                   R_WDOGCONTROL_RESEN_MASK)
41 REG32(WDOGINTCLR, 0xc)
42 REG32(WDOGRIS, 0x10)
43     FIELD(WDOGRIS, INT, 0, 1)
44 REG32(WDOGMIS, 0x14)
45 REG32(WDOGTEST, 0x418) /* only in Stellaris/Luminary version of the device */
46 REG32(WDOGLOCK, 0xc00)
47 #define WDOG_UNLOCK_VALUE 0x1ACCE551
48 REG32(WDOGITCR, 0xf00)
49     FIELD(WDOGITCR, ENABLE, 0, 1)
50 #define R_WDOGITCR_VALID_MASK R_WDOGITCR_ENABLE_MASK
51 REG32(WDOGITOP, 0xf04)
52     FIELD(WDOGITOP, WDOGRES, 0, 1)
53     FIELD(WDOGITOP, WDOGINT, 1, 1)
54 #define R_WDOGITOP_VALID_MASK (R_WDOGITOP_WDOGRES_MASK | \
55                                R_WDOGITOP_WDOGINT_MASK)
56 REG32(PID4, 0xfd0)
57 REG32(PID5, 0xfd4)
58 REG32(PID6, 0xfd8)
59 REG32(PID7, 0xfdc)
60 REG32(PID0, 0xfe0)
61 REG32(PID1, 0xfe4)
62 REG32(PID2, 0xfe8)
63 REG32(PID3, 0xfec)
64 REG32(CID0, 0xff0)
65 REG32(CID1, 0xff4)
66 REG32(CID2, 0xff8)
67 REG32(CID3, 0xffc)
68 
69 /* PID/CID values */
70 static const uint32_t cmsdk_apb_watchdog_id[] = {
71     0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
72     0x24, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
73     0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
74 };
75 
76 static const uint32_t luminary_watchdog_id[] = {
77     0x00, 0x00, 0x00, 0x00, /* PID4..PID7 */
78     0x05, 0x18, 0x18, 0x01, /* PID0..PID3 */
79     0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
80 };
81 
82 static bool cmsdk_apb_watchdog_intstatus(CMSDKAPBWatchdog *s)
83 {
84     /* Return masked interrupt status */
85     return s->intstatus && (s->control & R_WDOGCONTROL_INTEN_MASK);
86 }
87 
88 static bool cmsdk_apb_watchdog_resetstatus(CMSDKAPBWatchdog *s)
89 {
90     /* Return masked reset status */
91     return s->resetstatus && (s->control & R_WDOGCONTROL_RESEN_MASK);
92 }
93 
94 static void cmsdk_apb_watchdog_update(CMSDKAPBWatchdog *s)
95 {
96     bool wdogint;
97     bool wdogres;
98 
99     if (s->itcr) {
100         /*
101          * Not checking that !s->is_luminary since s->itcr can't be written
102          * when s->is_luminary in the first place.
103          */
104         wdogint = s->itop & R_WDOGITOP_WDOGINT_MASK;
105         wdogres = s->itop & R_WDOGITOP_WDOGRES_MASK;
106     } else {
107         wdogint = cmsdk_apb_watchdog_intstatus(s);
108         wdogres = cmsdk_apb_watchdog_resetstatus(s);
109     }
110 
111     qemu_set_irq(s->wdogint, wdogint);
112     if (wdogres) {
113         watchdog_perform_action();
114     }
115 }
116 
117 static uint64_t cmsdk_apb_watchdog_read(void *opaque, hwaddr offset,
118                                         unsigned size)
119 {
120     CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
121     uint64_t r;
122 
123     switch (offset) {
124     case A_WDOGLOAD:
125         r = ptimer_get_limit(s->timer);
126         break;
127     case A_WDOGVALUE:
128         r = ptimer_get_count(s->timer);
129         break;
130     case A_WDOGCONTROL:
131         r = s->control;
132         break;
133     case A_WDOGRIS:
134         r = s->intstatus;
135         break;
136     case A_WDOGMIS:
137         r = cmsdk_apb_watchdog_intstatus(s);
138         break;
139     case A_WDOGLOCK:
140         r = s->lock;
141         break;
142     case A_WDOGITCR:
143         if (s->is_luminary) {
144             goto bad_offset;
145         }
146         r = s->itcr;
147         break;
148     case A_PID4 ... A_CID3:
149         r = s->id[(offset - A_PID4) / 4];
150         break;
151     case A_WDOGINTCLR:
152     case A_WDOGITOP:
153         if (s->is_luminary) {
154             goto bad_offset;
155         }
156         qemu_log_mask(LOG_GUEST_ERROR,
157                       "CMSDK APB watchdog read: read of WO offset %x\n",
158                       (int)offset);
159         r = 0;
160         break;
161     case A_WDOGTEST:
162         if (!s->is_luminary) {
163             goto bad_offset;
164         }
165         qemu_log_mask(LOG_UNIMP,
166                       "Luminary watchdog read: stall not implemented\n");
167         r = 0;
168         break;
169     default:
170 bad_offset:
171         qemu_log_mask(LOG_GUEST_ERROR,
172                       "CMSDK APB watchdog read: bad offset %x\n", (int)offset);
173         r = 0;
174         break;
175     }
176     trace_cmsdk_apb_watchdog_read(offset, r, size);
177     return r;
178 }
179 
180 static void cmsdk_apb_watchdog_write(void *opaque, hwaddr offset,
181                                      uint64_t value, unsigned size)
182 {
183     CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
184 
185     trace_cmsdk_apb_watchdog_write(offset, value, size);
186 
187     if (s->lock && offset != A_WDOGLOCK) {
188         /* Write access is disabled via WDOGLOCK */
189         qemu_log_mask(LOG_GUEST_ERROR,
190                       "CMSDK APB watchdog write: write to locked watchdog\n");
191         return;
192     }
193 
194     switch (offset) {
195     case A_WDOGLOAD:
196         /*
197          * Reset the load value and the current count, and make sure
198          * we're counting.
199          */
200         ptimer_set_limit(s->timer, value, 1);
201         ptimer_run(s->timer, 0);
202         break;
203     case A_WDOGCONTROL:
204         if (s->is_luminary && 0 != (R_WDOGCONTROL_INTEN_MASK & s->control)) {
205             /*
206              * The Luminary version of this device ignores writes to
207              * this register after the guest has enabled interrupts
208              * (so they can only be disabled again via reset).
209              */
210             break;
211         }
212         s->control = value & R_WDOGCONTROL_VALID_MASK;
213         cmsdk_apb_watchdog_update(s);
214         break;
215     case A_WDOGINTCLR:
216         s->intstatus = 0;
217         ptimer_set_count(s->timer, ptimer_get_limit(s->timer));
218         cmsdk_apb_watchdog_update(s);
219         break;
220     case A_WDOGLOCK:
221         s->lock = (value != WDOG_UNLOCK_VALUE);
222         break;
223     case A_WDOGITCR:
224         if (s->is_luminary) {
225             goto bad_offset;
226         }
227         s->itcr = value & R_WDOGITCR_VALID_MASK;
228         cmsdk_apb_watchdog_update(s);
229         break;
230     case A_WDOGITOP:
231         if (s->is_luminary) {
232             goto bad_offset;
233         }
234         s->itop = value & R_WDOGITOP_VALID_MASK;
235         cmsdk_apb_watchdog_update(s);
236         break;
237     case A_WDOGVALUE:
238     case A_WDOGRIS:
239     case A_WDOGMIS:
240     case A_PID4 ... A_CID3:
241         qemu_log_mask(LOG_GUEST_ERROR,
242                       "CMSDK APB watchdog write: write to RO offset 0x%x\n",
243                       (int)offset);
244         break;
245     case A_WDOGTEST:
246         if (!s->is_luminary) {
247             goto bad_offset;
248         }
249         qemu_log_mask(LOG_UNIMP,
250                       "Luminary watchdog write: stall not implemented\n");
251         break;
252     default:
253 bad_offset:
254         qemu_log_mask(LOG_GUEST_ERROR,
255                       "CMSDK APB watchdog write: bad offset 0x%x\n",
256                       (int)offset);
257         break;
258     }
259 }
260 
261 static const MemoryRegionOps cmsdk_apb_watchdog_ops = {
262     .read = cmsdk_apb_watchdog_read,
263     .write = cmsdk_apb_watchdog_write,
264     .endianness = DEVICE_LITTLE_ENDIAN,
265     /* byte/halfword accesses are just zero-padded on reads and writes */
266     .impl.min_access_size = 4,
267     .impl.max_access_size = 4,
268     .valid.min_access_size = 1,
269     .valid.max_access_size = 4,
270 };
271 
272 static void cmsdk_apb_watchdog_tick(void *opaque)
273 {
274     CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(opaque);
275 
276     if (!s->intstatus) {
277         /* Count expired for the first time: raise interrupt */
278         s->intstatus = R_WDOGRIS_INT_MASK;
279     } else {
280         /* Count expired for the second time: raise reset and stop clock */
281         s->resetstatus = 1;
282         ptimer_stop(s->timer);
283     }
284     cmsdk_apb_watchdog_update(s);
285 }
286 
287 static void cmsdk_apb_watchdog_reset(DeviceState *dev)
288 {
289     CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(dev);
290 
291     trace_cmsdk_apb_watchdog_reset();
292     s->control = 0;
293     s->intstatus = 0;
294     s->lock = 0;
295     s->itcr = 0;
296     s->itop = 0;
297     s->resetstatus = 0;
298     /* Set the limit and the count */
299     ptimer_set_limit(s->timer, 0xffffffff, 1);
300     ptimer_run(s->timer, 0);
301 }
302 
303 static void cmsdk_apb_watchdog_init(Object *obj)
304 {
305     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
306     CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(obj);
307 
308     memory_region_init_io(&s->iomem, obj, &cmsdk_apb_watchdog_ops,
309                           s, "cmsdk-apb-watchdog", 0x1000);
310     sysbus_init_mmio(sbd, &s->iomem);
311     sysbus_init_irq(sbd, &s->wdogint);
312 
313     s->is_luminary = false;
314     s->id = cmsdk_apb_watchdog_id;
315 }
316 
317 static void cmsdk_apb_watchdog_realize(DeviceState *dev, Error **errp)
318 {
319     CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(dev);
320     QEMUBH *bh;
321 
322     if (s->wdogclk_frq == 0) {
323         error_setg(errp,
324                    "CMSDK APB watchdog: wdogclk-frq property must be set");
325         return;
326     }
327 
328     bh = qemu_bh_new(cmsdk_apb_watchdog_tick, s);
329     s->timer = ptimer_init(bh,
330                            PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD |
331                            PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT |
332                            PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
333                            PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
334 
335     ptimer_set_freq(s->timer, s->wdogclk_frq);
336 }
337 
338 static const VMStateDescription cmsdk_apb_watchdog_vmstate = {
339     .name = "cmsdk-apb-watchdog",
340     .version_id = 1,
341     .minimum_version_id = 1,
342     .fields = (VMStateField[]) {
343         VMSTATE_PTIMER(timer, CMSDKAPBWatchdog),
344         VMSTATE_UINT32(control, CMSDKAPBWatchdog),
345         VMSTATE_UINT32(intstatus, CMSDKAPBWatchdog),
346         VMSTATE_UINT32(lock, CMSDKAPBWatchdog),
347         VMSTATE_UINT32(itcr, CMSDKAPBWatchdog),
348         VMSTATE_UINT32(itop, CMSDKAPBWatchdog),
349         VMSTATE_UINT32(resetstatus, CMSDKAPBWatchdog),
350         VMSTATE_END_OF_LIST()
351     }
352 };
353 
354 static Property cmsdk_apb_watchdog_properties[] = {
355     DEFINE_PROP_UINT32("wdogclk-frq", CMSDKAPBWatchdog, wdogclk_frq, 0),
356     DEFINE_PROP_END_OF_LIST(),
357 };
358 
359 static void cmsdk_apb_watchdog_class_init(ObjectClass *klass, void *data)
360 {
361     DeviceClass *dc = DEVICE_CLASS(klass);
362 
363     dc->realize = cmsdk_apb_watchdog_realize;
364     dc->vmsd = &cmsdk_apb_watchdog_vmstate;
365     dc->reset = cmsdk_apb_watchdog_reset;
366     dc->props = cmsdk_apb_watchdog_properties;
367 }
368 
369 static const TypeInfo cmsdk_apb_watchdog_info = {
370     .name = TYPE_CMSDK_APB_WATCHDOG,
371     .parent = TYPE_SYS_BUS_DEVICE,
372     .instance_size = sizeof(CMSDKAPBWatchdog),
373     .instance_init = cmsdk_apb_watchdog_init,
374     .class_init = cmsdk_apb_watchdog_class_init,
375 };
376 
377 static void luminary_watchdog_init(Object *obj)
378 {
379     CMSDKAPBWatchdog *s = CMSDK_APB_WATCHDOG(obj);
380 
381     s->is_luminary = true;
382     s->id = luminary_watchdog_id;
383 }
384 
385 static const TypeInfo luminary_watchdog_info = {
386     .name = TYPE_LUMINARY_WATCHDOG,
387     .parent = TYPE_CMSDK_APB_WATCHDOG,
388     .instance_init = luminary_watchdog_init
389 };
390 
391 static void cmsdk_apb_watchdog_register_types(void)
392 {
393     type_register_static(&cmsdk_apb_watchdog_info);
394     type_register_static(&luminary_watchdog_info);
395 }
396 
397 type_init(cmsdk_apb_watchdog_register_types);
398