xref: /qemu/hw/watchdog/allwinner-wdt.c (revision 2abf0da2)
1 /*
2  * Allwinner Watchdog emulation
3  *
4  * Copyright (C) 2023 Strahinja Jankovic <strahinja.p.jankovic@gmail.com>
5  *
6  *  This file is derived from Allwinner RTC,
7  *  by Niek Linnenbank.
8  *
9  * This program is free software: you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation, either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "qemu/osdep.h"
24 #include "qemu/log.h"
25 #include "qemu/units.h"
26 #include "qemu/module.h"
27 #include "trace.h"
28 #include "hw/sysbus.h"
29 #include "hw/registerfields.h"
30 #include "hw/watchdog/allwinner-wdt.h"
31 #include "sysemu/watchdog.h"
32 #include "migration/vmstate.h"
33 
34 /* WDT registers */
35 enum {
36     REG_IRQ_EN = 0,     /* Watchdog interrupt enable */
37     REG_IRQ_STA,        /* Watchdog interrupt status */
38     REG_CTRL,           /* Watchdog control register */
39     REG_CFG,            /* Watchdog configuration register */
40     REG_MODE,           /* Watchdog mode register */
41 };
42 
43 /* Universal WDT register flags */
44 #define WDT_RESTART_MASK    (1 << 0)
45 #define WDT_EN_MASK         (1 << 0)
46 
47 /* sun4i specific WDT register flags */
48 #define RST_EN_SUN4I_MASK       (1 << 1)
49 #define INTV_VALUE_SUN4I_SHIFT  (3)
50 #define INTV_VALUE_SUN4I_MASK   (0xfu << INTV_VALUE_SUN4I_SHIFT)
51 
52 /* sun6i specific WDT register flags */
53 #define RST_EN_SUN6I_MASK       (1 << 0)
54 #define KEY_FIELD_SUN6I_SHIFT   (1)
55 #define KEY_FIELD_SUN6I_MASK    (0xfffu << KEY_FIELD_SUN6I_SHIFT)
56 #define KEY_FIELD_SUN6I         (0xA57u)
57 #define INTV_VALUE_SUN6I_SHIFT  (4)
58 #define INTV_VALUE_SUN6I_MASK   (0xfu << INTV_VALUE_SUN6I_SHIFT)
59 
60 /* Map of INTV_VALUE to 0.5s units. */
61 static const uint8_t allwinner_wdt_count_map[] = {
62     1,
63     2,
64     4,
65     6,
66     8,
67     10,
68     12,
69     16,
70     20,
71     24,
72     28,
73     32
74 };
75 
76 /* WDT sun4i register map (offset to name) */
77 const uint8_t allwinner_wdt_sun4i_regmap[] = {
78     [0x0000] = REG_CTRL,
79     [0x0004] = REG_MODE,
80 };
81 
82 /* WDT sun6i register map (offset to name) */
83 const uint8_t allwinner_wdt_sun6i_regmap[] = {
84     [0x0000] = REG_IRQ_EN,
85     [0x0004] = REG_IRQ_STA,
86     [0x0010] = REG_CTRL,
87     [0x0014] = REG_CFG,
88     [0x0018] = REG_MODE,
89 };
90 
91 static bool allwinner_wdt_sun4i_read(AwWdtState *s, uint32_t offset)
92 {
93     /* no sun4i specific registers currently implemented */
94     return false;
95 }
96 
97 static bool allwinner_wdt_sun4i_write(AwWdtState *s, uint32_t offset,
98                                       uint32_t data)
99 {
100     /* no sun4i specific registers currently implemented */
101     return false;
102 }
103 
104 static bool allwinner_wdt_sun4i_can_reset_system(AwWdtState *s)
105 {
106     if (s->regs[REG_MODE] & RST_EN_SUN4I_MASK) {
107         return true;
108     } else {
109         return false;
110     }
111 }
112 
113 static bool allwinner_wdt_sun4i_is_key_valid(AwWdtState *s, uint32_t val)
114 {
115     /* sun4i has no key */
116     return true;
117 }
118 
119 static uint8_t allwinner_wdt_sun4i_get_intv_value(AwWdtState *s)
120 {
121     return ((s->regs[REG_MODE] & INTV_VALUE_SUN4I_MASK) >>
122             INTV_VALUE_SUN4I_SHIFT);
123 }
124 
125 static bool allwinner_wdt_sun6i_read(AwWdtState *s, uint32_t offset)
126 {
127     const AwWdtClass *c = AW_WDT_GET_CLASS(s);
128 
129     switch (c->regmap[offset]) {
130     case REG_IRQ_EN:
131     case REG_IRQ_STA:
132     case REG_CFG:
133         return true;
134     default:
135         break;
136     }
137     return false;
138 }
139 
140 static bool allwinner_wdt_sun6i_write(AwWdtState *s, uint32_t offset,
141                                       uint32_t data)
142 {
143     const AwWdtClass *c = AW_WDT_GET_CLASS(s);
144 
145     switch (c->regmap[offset]) {
146     case REG_IRQ_EN:
147     case REG_IRQ_STA:
148     case REG_CFG:
149         return true;
150     default:
151         break;
152     }
153     return false;
154 }
155 
156 static bool allwinner_wdt_sun6i_can_reset_system(AwWdtState *s)
157 {
158     if (s->regs[REG_CFG] & RST_EN_SUN6I_MASK) {
159         return true;
160     } else {
161         return false;
162     }
163 }
164 
165 static bool allwinner_wdt_sun6i_is_key_valid(AwWdtState *s, uint32_t val)
166 {
167     uint16_t key = (val & KEY_FIELD_SUN6I_MASK) >> KEY_FIELD_SUN6I_SHIFT;
168     return (key == KEY_FIELD_SUN6I);
169 }
170 
171 static uint8_t allwinner_wdt_sun6i_get_intv_value(AwWdtState *s)
172 {
173     return ((s->regs[REG_MODE] & INTV_VALUE_SUN6I_MASK) >>
174             INTV_VALUE_SUN6I_SHIFT);
175 }
176 
177 static void allwinner_wdt_update_timer(AwWdtState *s)
178 {
179     const AwWdtClass *c = AW_WDT_GET_CLASS(s);
180     uint8_t count = c->get_intv_value(s);
181 
182     ptimer_transaction_begin(s->timer);
183     ptimer_stop(s->timer);
184 
185     /* Use map to convert. */
186     if (count < sizeof(allwinner_wdt_count_map)) {
187         ptimer_set_count(s->timer, allwinner_wdt_count_map[count]);
188     } else {
189         qemu_log_mask(LOG_GUEST_ERROR, "%s: incorrect INTV_VALUE 0x%02x\n",
190                 __func__, count);
191     }
192 
193     ptimer_run(s->timer, 1);
194     ptimer_transaction_commit(s->timer);
195 
196     trace_allwinner_wdt_update_timer(count);
197 }
198 
199 static uint64_t allwinner_wdt_read(void *opaque, hwaddr offset,
200                                        unsigned size)
201 {
202     AwWdtState *s = AW_WDT(opaque);
203     const AwWdtClass *c = AW_WDT_GET_CLASS(s);
204     uint64_t r;
205 
206     if (offset >= c->regmap_size) {
207         qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
208                       __func__, (uint32_t)offset);
209         return 0;
210     }
211 
212     switch (c->regmap[offset]) {
213     case REG_CTRL:
214     case REG_MODE:
215         r = s->regs[c->regmap[offset]];
216         break;
217     default:
218         if (!c->read(s, offset)) {
219             qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n",
220                             __func__, (uint32_t)offset);
221             return 0;
222         }
223         r = s->regs[c->regmap[offset]];
224         break;
225     }
226 
227     trace_allwinner_wdt_read(offset, r, size);
228 
229     return r;
230 }
231 
232 static void allwinner_wdt_write(void *opaque, hwaddr offset,
233                                    uint64_t val, unsigned size)
234 {
235     AwWdtState *s = AW_WDT(opaque);
236     const AwWdtClass *c = AW_WDT_GET_CLASS(s);
237     uint32_t old_val;
238 
239     if (offset >= c->regmap_size) {
240         qemu_log_mask(LOG_GUEST_ERROR, "%s: out-of-bounds offset 0x%04x\n",
241                       __func__, (uint32_t)offset);
242         return;
243     }
244 
245    trace_allwinner_wdt_write(offset, val, size);
246 
247     switch (c->regmap[offset]) {
248     case REG_CTRL:
249         if (c->is_key_valid(s, val)) {
250             if (val & WDT_RESTART_MASK) {
251                 /* Kick timer */
252                 allwinner_wdt_update_timer(s);
253             }
254         }
255         break;
256     case REG_MODE:
257         old_val = s->regs[REG_MODE];
258         s->regs[REG_MODE] = (uint32_t)val;
259 
260         /* Check for rising edge on WDOG_MODE_EN */
261         if ((s->regs[REG_MODE] & ~old_val) & WDT_EN_MASK) {
262             allwinner_wdt_update_timer(s);
263         }
264         break;
265     default:
266         if (!c->write(s, offset, val)) {
267             qemu_log_mask(LOG_UNIMP, "%s: unimplemented register 0x%04x\n",
268                           __func__, (uint32_t)offset);
269         }
270         s->regs[c->regmap[offset]] = (uint32_t)val;
271         break;
272     }
273 }
274 
275 static const MemoryRegionOps allwinner_wdt_ops = {
276     .read = allwinner_wdt_read,
277     .write = allwinner_wdt_write,
278     .endianness = DEVICE_NATIVE_ENDIAN,
279     .valid = {
280         .min_access_size = 4,
281         .max_access_size = 4,
282     },
283     .impl.min_access_size = 4,
284 };
285 
286 static void allwinner_wdt_expired(void *opaque)
287 {
288     AwWdtState *s = AW_WDT(opaque);
289     const AwWdtClass *c = AW_WDT_GET_CLASS(s);
290 
291     bool enabled = s->regs[REG_MODE] & WDT_EN_MASK;
292     bool reset_enabled = c->can_reset_system(s);
293 
294     trace_allwinner_wdt_expired(enabled, reset_enabled);
295 
296     /* Perform watchdog action if watchdog is enabled and can trigger reset */
297     if (enabled && reset_enabled) {
298         watchdog_perform_action();
299     }
300 }
301 
302 static void allwinner_wdt_reset_enter(Object *obj, ResetType type)
303 {
304     AwWdtState *s = AW_WDT(obj);
305 
306     trace_allwinner_wdt_reset_enter();
307 
308     /* Clear registers */
309     memset(s->regs, 0, sizeof(s->regs));
310 }
311 
312 static const VMStateDescription allwinner_wdt_vmstate = {
313     .name = "allwinner-wdt",
314     .version_id = 1,
315     .minimum_version_id = 1,
316     .fields = (const VMStateField[]) {
317         VMSTATE_PTIMER(timer, AwWdtState),
318         VMSTATE_UINT32_ARRAY(regs, AwWdtState, AW_WDT_REGS_NUM),
319         VMSTATE_END_OF_LIST()
320     }
321 };
322 
323 static void allwinner_wdt_init(Object *obj)
324 {
325     SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
326     AwWdtState *s = AW_WDT(obj);
327     const AwWdtClass *c = AW_WDT_GET_CLASS(s);
328 
329     /* Memory mapping */
330     memory_region_init_io(&s->iomem, OBJECT(s), &allwinner_wdt_ops, s,
331                           TYPE_AW_WDT, c->regmap_size * 4);
332     sysbus_init_mmio(sbd, &s->iomem);
333 }
334 
335 static void allwinner_wdt_realize(DeviceState *dev, Error **errp)
336 {
337     AwWdtState *s = AW_WDT(dev);
338 
339     s->timer = ptimer_init(allwinner_wdt_expired, s,
340                            PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
341                            PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
342                            PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
343 
344     ptimer_transaction_begin(s->timer);
345     /* Set to 2Hz (0.5s period); other periods are multiples of 0.5s. */
346     ptimer_set_freq(s->timer, 2);
347     ptimer_set_limit(s->timer, 0xff, 1);
348     ptimer_transaction_commit(s->timer);
349 }
350 
351 static void allwinner_wdt_class_init(ObjectClass *klass, void *data)
352 {
353     DeviceClass *dc = DEVICE_CLASS(klass);
354     ResettableClass *rc = RESETTABLE_CLASS(klass);
355 
356     rc->phases.enter = allwinner_wdt_reset_enter;
357     dc->realize = allwinner_wdt_realize;
358     dc->vmsd = &allwinner_wdt_vmstate;
359 }
360 
361 static void allwinner_wdt_sun4i_class_init(ObjectClass *klass, void *data)
362 {
363     AwWdtClass *awc = AW_WDT_CLASS(klass);
364 
365     awc->regmap = allwinner_wdt_sun4i_regmap;
366     awc->regmap_size = sizeof(allwinner_wdt_sun4i_regmap);
367     awc->read = allwinner_wdt_sun4i_read;
368     awc->write = allwinner_wdt_sun4i_write;
369     awc->can_reset_system = allwinner_wdt_sun4i_can_reset_system;
370     awc->is_key_valid = allwinner_wdt_sun4i_is_key_valid;
371     awc->get_intv_value = allwinner_wdt_sun4i_get_intv_value;
372 }
373 
374 static void allwinner_wdt_sun6i_class_init(ObjectClass *klass, void *data)
375 {
376     AwWdtClass *awc = AW_WDT_CLASS(klass);
377 
378     awc->regmap = allwinner_wdt_sun6i_regmap;
379     awc->regmap_size = sizeof(allwinner_wdt_sun6i_regmap);
380     awc->read = allwinner_wdt_sun6i_read;
381     awc->write = allwinner_wdt_sun6i_write;
382     awc->can_reset_system = allwinner_wdt_sun6i_can_reset_system;
383     awc->is_key_valid = allwinner_wdt_sun6i_is_key_valid;
384     awc->get_intv_value = allwinner_wdt_sun6i_get_intv_value;
385 }
386 
387 static const TypeInfo allwinner_wdt_info = {
388     .name          = TYPE_AW_WDT,
389     .parent        = TYPE_SYS_BUS_DEVICE,
390     .instance_init = allwinner_wdt_init,
391     .instance_size = sizeof(AwWdtState),
392     .class_init    = allwinner_wdt_class_init,
393     .class_size    = sizeof(AwWdtClass),
394     .abstract      = true,
395 };
396 
397 static const TypeInfo allwinner_wdt_sun4i_info = {
398     .name          = TYPE_AW_WDT_SUN4I,
399     .parent        = TYPE_AW_WDT,
400     .class_init    = allwinner_wdt_sun4i_class_init,
401 };
402 
403 static const TypeInfo allwinner_wdt_sun6i_info = {
404     .name          = TYPE_AW_WDT_SUN6I,
405     .parent        = TYPE_AW_WDT,
406     .class_init    = allwinner_wdt_sun6i_class_init,
407 };
408 
409 static void allwinner_wdt_register(void)
410 {
411     type_register_static(&allwinner_wdt_info);
412     type_register_static(&allwinner_wdt_sun4i_info);
413     type_register_static(&allwinner_wdt_sun6i_info);
414 }
415 
416 type_init(allwinner_wdt_register)
417