xref: /openbmc/qemu/hw/watchdog/wdt_aspeed.c (revision 59a3a1c0)
1 /*
2  * ASPEED Watchdog Controller
3  *
4  * Copyright (C) 2016-2017 IBM Corp.
5  *
6  * This code is licensed under the GPL version 2 or later. See the
7  * COPYING file in the top-level directory.
8  */
9 
10 #include "qemu/osdep.h"
11 
12 #include "qapi/error.h"
13 #include "qemu/log.h"
14 #include "qemu/module.h"
15 #include "qemu/timer.h"
16 #include "sysemu/watchdog.h"
17 #include "hw/misc/aspeed_scu.h"
18 #include "hw/qdev-properties.h"
19 #include "hw/sysbus.h"
20 #include "hw/watchdog/wdt_aspeed.h"
21 #include "migration/vmstate.h"
22 
23 #define WDT_STATUS                      (0x00 / 4)
24 #define WDT_RELOAD_VALUE                (0x04 / 4)
25 #define WDT_RESTART                     (0x08 / 4)
26 #define WDT_CTRL                        (0x0C / 4)
27 #define   WDT_CTRL_RESET_MODE_SOC       (0x00 << 5)
28 #define   WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5)
29 #define   WDT_CTRL_1MHZ_CLK             BIT(4)
30 #define   WDT_CTRL_WDT_EXT              BIT(3)
31 #define   WDT_CTRL_WDT_INTR             BIT(2)
32 #define   WDT_CTRL_RESET_SYSTEM         BIT(1)
33 #define   WDT_CTRL_ENABLE               BIT(0)
34 #define WDT_RESET_WIDTH                 (0x18 / 4)
35 #define   WDT_RESET_WIDTH_ACTIVE_HIGH   BIT(31)
36 #define     WDT_POLARITY_MASK           (0xFF << 24)
37 #define     WDT_ACTIVE_HIGH_MAGIC       (0xA5 << 24)
38 #define     WDT_ACTIVE_LOW_MAGIC        (0x5A << 24)
39 #define   WDT_RESET_WIDTH_PUSH_PULL     BIT(30)
40 #define     WDT_DRIVE_TYPE_MASK         (0xFF << 24)
41 #define     WDT_PUSH_PULL_MAGIC         (0xA8 << 24)
42 #define     WDT_OPEN_DRAIN_MAGIC        (0x8A << 24)
43 
44 #define WDT_TIMEOUT_STATUS              (0x10 / 4)
45 #define WDT_TIMEOUT_CLEAR               (0x14 / 4)
46 
47 #define WDT_RESTART_MAGIC               0x4755
48 
49 #define SCU_RESET_CONTROL1              (0x04 / 4)
50 #define    SCU_RESET_SDRAM              BIT(0)
51 
52 static bool aspeed_wdt_is_enabled(const AspeedWDTState *s)
53 {
54     return s->regs[WDT_CTRL] & WDT_CTRL_ENABLE;
55 }
56 
57 static bool is_ast2500(const AspeedWDTState *s)
58 {
59     switch (s->silicon_rev) {
60     case AST2500_A0_SILICON_REV:
61     case AST2500_A1_SILICON_REV:
62         return true;
63     case AST2400_A0_SILICON_REV:
64     case AST2400_A1_SILICON_REV:
65     default:
66         break;
67     }
68 
69     return false;
70 }
71 
72 static uint64_t aspeed_wdt_read(void *opaque, hwaddr offset, unsigned size)
73 {
74     AspeedWDTState *s = ASPEED_WDT(opaque);
75 
76     offset >>= 2;
77 
78     switch (offset) {
79     case WDT_STATUS:
80         return s->regs[WDT_STATUS];
81     case WDT_RELOAD_VALUE:
82         return s->regs[WDT_RELOAD_VALUE];
83     case WDT_RESTART:
84         qemu_log_mask(LOG_GUEST_ERROR,
85                       "%s: read from write-only reg at offset 0x%"
86                       HWADDR_PRIx "\n", __func__, offset);
87         return 0;
88     case WDT_CTRL:
89         return s->regs[WDT_CTRL];
90     case WDT_RESET_WIDTH:
91         return s->regs[WDT_RESET_WIDTH];
92     case WDT_TIMEOUT_STATUS:
93     case WDT_TIMEOUT_CLEAR:
94         qemu_log_mask(LOG_UNIMP,
95                       "%s: uninmplemented read at offset 0x%" HWADDR_PRIx "\n",
96                       __func__, offset);
97         return 0;
98     default:
99         qemu_log_mask(LOG_GUEST_ERROR,
100                       "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
101                       __func__, offset);
102         return 0;
103     }
104 
105 }
106 
107 static void aspeed_wdt_reload(AspeedWDTState *s, bool pclk)
108 {
109     uint64_t reload;
110 
111     if (pclk) {
112         reload = muldiv64(s->regs[WDT_RELOAD_VALUE], NANOSECONDS_PER_SECOND,
113                           s->pclk_freq);
114     } else {
115         reload = s->regs[WDT_RELOAD_VALUE] * 1000ULL;
116     }
117 
118     if (aspeed_wdt_is_enabled(s)) {
119         timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + reload);
120     }
121 }
122 
123 static void aspeed_wdt_write(void *opaque, hwaddr offset, uint64_t data,
124                              unsigned size)
125 {
126     AspeedWDTState *s = ASPEED_WDT(opaque);
127     bool enable = data & WDT_CTRL_ENABLE;
128 
129     offset >>= 2;
130 
131     switch (offset) {
132     case WDT_STATUS:
133         qemu_log_mask(LOG_GUEST_ERROR,
134                       "%s: write to read-only reg at offset 0x%"
135                       HWADDR_PRIx "\n", __func__, offset);
136         break;
137     case WDT_RELOAD_VALUE:
138         s->regs[WDT_RELOAD_VALUE] = data;
139         break;
140     case WDT_RESTART:
141         if ((data & 0xFFFF) == WDT_RESTART_MAGIC) {
142             s->regs[WDT_STATUS] = s->regs[WDT_RELOAD_VALUE];
143             aspeed_wdt_reload(s, !(data & WDT_CTRL_1MHZ_CLK));
144         }
145         break;
146     case WDT_CTRL:
147         if (enable && !aspeed_wdt_is_enabled(s)) {
148             s->regs[WDT_CTRL] = data;
149             aspeed_wdt_reload(s, !(data & WDT_CTRL_1MHZ_CLK));
150         } else if (!enable && aspeed_wdt_is_enabled(s)) {
151             s->regs[WDT_CTRL] = data;
152             timer_del(s->timer);
153         }
154         break;
155     case WDT_RESET_WIDTH:
156     {
157         uint32_t property = data & WDT_POLARITY_MASK;
158 
159         if (property && is_ast2500(s)) {
160             if (property == WDT_ACTIVE_HIGH_MAGIC) {
161                 s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_ACTIVE_HIGH;
162             } else if (property == WDT_ACTIVE_LOW_MAGIC) {
163                 s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_ACTIVE_HIGH;
164             } else if (property == WDT_PUSH_PULL_MAGIC) {
165                 s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_PUSH_PULL;
166             } else if (property == WDT_OPEN_DRAIN_MAGIC) {
167                 s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_PUSH_PULL;
168             }
169         }
170         s->regs[WDT_RESET_WIDTH] &= ~s->ext_pulse_width_mask;
171         s->regs[WDT_RESET_WIDTH] |= data & s->ext_pulse_width_mask;
172         break;
173     }
174     case WDT_TIMEOUT_STATUS:
175     case WDT_TIMEOUT_CLEAR:
176         qemu_log_mask(LOG_UNIMP,
177                       "%s: uninmplemented write at offset 0x%" HWADDR_PRIx "\n",
178                       __func__, offset);
179         break;
180     default:
181         qemu_log_mask(LOG_GUEST_ERROR,
182                       "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
183                       __func__, offset);
184     }
185     return;
186 }
187 
188 static WatchdogTimerModel model = {
189     .wdt_name = TYPE_ASPEED_WDT,
190     .wdt_description = "Aspeed watchdog device",
191 };
192 
193 static const VMStateDescription vmstate_aspeed_wdt = {
194     .name = "vmstate_aspeed_wdt",
195     .version_id = 0,
196     .minimum_version_id = 0,
197     .fields = (VMStateField[]) {
198         VMSTATE_TIMER_PTR(timer, AspeedWDTState),
199         VMSTATE_UINT32_ARRAY(regs, AspeedWDTState, ASPEED_WDT_REGS_MAX),
200         VMSTATE_END_OF_LIST()
201     }
202 };
203 
204 static const MemoryRegionOps aspeed_wdt_ops = {
205     .read = aspeed_wdt_read,
206     .write = aspeed_wdt_write,
207     .endianness = DEVICE_LITTLE_ENDIAN,
208     .valid.min_access_size = 4,
209     .valid.max_access_size = 4,
210     .valid.unaligned = false,
211 };
212 
213 static void aspeed_wdt_reset(DeviceState *dev)
214 {
215     AspeedWDTState *s = ASPEED_WDT(dev);
216 
217     s->regs[WDT_STATUS] = 0x3EF1480;
218     s->regs[WDT_RELOAD_VALUE] = 0x03EF1480;
219     s->regs[WDT_RESTART] = 0;
220     s->regs[WDT_CTRL] = 0;
221     s->regs[WDT_RESET_WIDTH] = 0xFF;
222 
223     timer_del(s->timer);
224 }
225 
226 static void aspeed_wdt_timer_expired(void *dev)
227 {
228     AspeedWDTState *s = ASPEED_WDT(dev);
229 
230     /* Do not reset on SDRAM controller reset */
231     if (s->scu->regs[SCU_RESET_CONTROL1] & SCU_RESET_SDRAM) {
232         timer_del(s->timer);
233         s->regs[WDT_CTRL] = 0;
234         return;
235     }
236 
237     qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n");
238     watchdog_perform_action();
239     timer_del(s->timer);
240 }
241 
242 #define PCLK_HZ 24000000
243 
244 static void aspeed_wdt_realize(DeviceState *dev, Error **errp)
245 {
246     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
247     AspeedWDTState *s = ASPEED_WDT(dev);
248     Error *err = NULL;
249     Object *obj;
250 
251     obj = object_property_get_link(OBJECT(dev), "scu", &err);
252     if (!obj) {
253         error_propagate(errp, err);
254         error_prepend(errp, "required link 'scu' not found: ");
255         return;
256     }
257     s->scu = ASPEED_SCU(obj);
258 
259     if (!is_supported_silicon_rev(s->silicon_rev)) {
260         error_setg(errp, "Unknown silicon revision: 0x%" PRIx32,
261                 s->silicon_rev);
262         return;
263     }
264 
265     switch (s->silicon_rev) {
266     case AST2400_A0_SILICON_REV:
267     case AST2400_A1_SILICON_REV:
268         s->ext_pulse_width_mask = 0xff;
269         break;
270     case AST2500_A0_SILICON_REV:
271     case AST2500_A1_SILICON_REV:
272         s->ext_pulse_width_mask = 0xfffff;
273         break;
274     default:
275         g_assert_not_reached();
276     }
277 
278     s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, aspeed_wdt_timer_expired, dev);
279 
280     /* FIXME: This setting should be derived from the SCU hw strapping
281      * register SCU70
282      */
283     s->pclk_freq = PCLK_HZ;
284 
285     memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_wdt_ops, s,
286                           TYPE_ASPEED_WDT, ASPEED_WDT_REGS_MAX * 4);
287     sysbus_init_mmio(sbd, &s->iomem);
288 }
289 
290 static Property aspeed_wdt_properties[] = {
291     DEFINE_PROP_UINT32("silicon-rev", AspeedWDTState, silicon_rev, 0),
292     DEFINE_PROP_END_OF_LIST(),
293 };
294 
295 static void aspeed_wdt_class_init(ObjectClass *klass, void *data)
296 {
297     DeviceClass *dc = DEVICE_CLASS(klass);
298 
299     dc->realize = aspeed_wdt_realize;
300     dc->reset = aspeed_wdt_reset;
301     set_bit(DEVICE_CATEGORY_MISC, dc->categories);
302     dc->vmsd = &vmstate_aspeed_wdt;
303     dc->props = aspeed_wdt_properties;
304 }
305 
306 static const TypeInfo aspeed_wdt_info = {
307     .parent = TYPE_SYS_BUS_DEVICE,
308     .name  = TYPE_ASPEED_WDT,
309     .instance_size  = sizeof(AspeedWDTState),
310     .class_init = aspeed_wdt_class_init,
311 };
312 
313 static void wdt_aspeed_register_types(void)
314 {
315     watchdog_add_model(&model);
316     type_register_static(&aspeed_wdt_info);
317 }
318 
319 type_init(wdt_aspeed_register_types)
320