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 "system/watchdog.h"
17 #include "hw/qdev-properties.h"
18 #include "hw/sysbus.h"
19 #include "hw/watchdog/wdt_aspeed.h"
20 #include "migration/vmstate.h"
21 #include "trace.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 #define WDT_RESET_MASK1 (0x1c / 4)
44 #define WDT_RESET_MASK2 (0x20 / 4)
45
46 #define WDT_SW_RESET_CTRL (0x24 / 4)
47 #define WDT_SW_RESET_MASK1 (0x28 / 4)
48 #define WDT_SW_RESET_MASK2 (0x2c / 4)
49
50 #define WDT_TIMEOUT_STATUS (0x10 / 4)
51 #define WDT_TIMEOUT_CLEAR (0x14 / 4)
52
53 #define WDT_RESTART_MAGIC 0x4755
54 #define WDT_SW_RESET_ENABLE 0xAEEDF123
55
56 #define AST2600_SCU_RESET_CONTROL1 (0x40 / 4)
57 #define SCU_RESET_CONTROL1 (0x04 / 4)
58 #define SCU_RESET_SDRAM BIT(0)
59
aspeed_wdt_is_soc_reset_mode(const AspeedWDTState * s)60 static bool aspeed_wdt_is_soc_reset_mode(const AspeedWDTState *s)
61 {
62 uint32_t mode;
63
64 mode = extract32(s->regs[WDT_CTRL], 5, 2);
65 return (mode == WDT_CTRL_RESET_MODE_SOC);
66 }
67
aspeed_wdt_is_enabled(const AspeedWDTState * s)68 static bool aspeed_wdt_is_enabled(const AspeedWDTState *s)
69 {
70 return s->regs[WDT_CTRL] & WDT_CTRL_ENABLE;
71 }
72
aspeed_wdt_read(void * opaque,hwaddr offset,unsigned size)73 static uint64_t aspeed_wdt_read(void *opaque, hwaddr offset, unsigned size)
74 {
75 AspeedWDTState *s = ASPEED_WDT(opaque);
76
77 trace_aspeed_wdt_read(offset, size);
78
79 offset >>= 2;
80
81 switch (offset) {
82 case WDT_STATUS:
83 return s->regs[WDT_STATUS];
84 case WDT_RELOAD_VALUE:
85 return s->regs[WDT_RELOAD_VALUE];
86 case WDT_RESTART:
87 qemu_log_mask(LOG_GUEST_ERROR,
88 "%s: read from write-only reg at offset 0x%"
89 HWADDR_PRIx "\n", __func__, offset);
90 return 0;
91 case WDT_CTRL:
92 return s->regs[WDT_CTRL];
93 case WDT_RESET_WIDTH:
94 return s->regs[WDT_RESET_WIDTH];
95 case WDT_RESET_MASK1:
96 return s->regs[WDT_RESET_MASK1];
97 case WDT_TIMEOUT_STATUS:
98 case WDT_TIMEOUT_CLEAR:
99 case WDT_RESET_MASK2:
100 case WDT_SW_RESET_CTRL:
101 case WDT_SW_RESET_MASK1:
102 case WDT_SW_RESET_MASK2:
103 qemu_log_mask(LOG_UNIMP,
104 "%s: uninmplemented read at offset 0x%" HWADDR_PRIx "\n",
105 __func__, offset);
106 return 0;
107 default:
108 qemu_log_mask(LOG_GUEST_ERROR,
109 "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
110 __func__, offset);
111 return 0;
112 }
113
114 }
115
aspeed_wdt_reload(AspeedWDTState * s)116 static void aspeed_wdt_reload(AspeedWDTState *s)
117 {
118 uint64_t reload;
119
120 if (!(s->regs[WDT_CTRL] & WDT_CTRL_1MHZ_CLK)) {
121 reload = muldiv64(s->regs[WDT_RELOAD_VALUE], NANOSECONDS_PER_SECOND,
122 s->pclk_freq);
123 } else {
124 reload = s->regs[WDT_RELOAD_VALUE] * 1000ULL;
125 }
126
127 if (aspeed_wdt_is_enabled(s)) {
128 timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + reload);
129 }
130 }
131
aspeed_wdt_reload_1mhz(AspeedWDTState * s)132 static void aspeed_wdt_reload_1mhz(AspeedWDTState *s)
133 {
134 uint64_t reload = s->regs[WDT_RELOAD_VALUE] * 1000ULL;
135
136 if (aspeed_wdt_is_enabled(s)) {
137 timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + reload);
138 }
139 }
140
aspeed_2400_sanitize_ctrl(uint64_t data)141 static uint64_t aspeed_2400_sanitize_ctrl(uint64_t data)
142 {
143 return data & 0xffff;
144 }
145
aspeed_2500_sanitize_ctrl(uint64_t data)146 static uint64_t aspeed_2500_sanitize_ctrl(uint64_t data)
147 {
148 return (data & ~(0xfUL << 8)) | WDT_CTRL_1MHZ_CLK;
149 }
150
aspeed_2600_sanitize_ctrl(uint64_t data)151 static uint64_t aspeed_2600_sanitize_ctrl(uint64_t data)
152 {
153 return data & ~(0x7UL << 7);
154 }
155
aspeed_wdt_write(void * opaque,hwaddr offset,uint64_t data,unsigned size)156 static void aspeed_wdt_write(void *opaque, hwaddr offset, uint64_t data,
157 unsigned size)
158 {
159 AspeedWDTState *s = ASPEED_WDT(opaque);
160 AspeedWDTClass *awc = ASPEED_WDT_GET_CLASS(s);
161 bool enable;
162
163 trace_aspeed_wdt_write(offset, size, data);
164
165 offset >>= 2;
166
167 switch (offset) {
168 case WDT_STATUS:
169 qemu_log_mask(LOG_GUEST_ERROR,
170 "%s: write to read-only reg at offset 0x%"
171 HWADDR_PRIx "\n", __func__, offset);
172 break;
173 case WDT_RELOAD_VALUE:
174 s->regs[WDT_RELOAD_VALUE] = data;
175 break;
176 case WDT_RESTART:
177 if ((data & 0xFFFF) == WDT_RESTART_MAGIC) {
178 s->regs[WDT_STATUS] = s->regs[WDT_RELOAD_VALUE];
179 awc->wdt_reload(s);
180 }
181 break;
182 case WDT_CTRL:
183 data = awc->sanitize_ctrl(data);
184 enable = data & WDT_CTRL_ENABLE;
185 if (enable && !aspeed_wdt_is_enabled(s)) {
186 s->regs[WDT_CTRL] = data;
187 awc->wdt_reload(s);
188 } else if (!enable && aspeed_wdt_is_enabled(s)) {
189 s->regs[WDT_CTRL] = data;
190 timer_del(s->timer);
191 } else {
192 s->regs[WDT_CTRL] = data;
193 }
194 break;
195 case WDT_RESET_WIDTH:
196 if (awc->reset_pulse) {
197 awc->reset_pulse(s, data & WDT_POLARITY_MASK);
198 }
199 s->regs[WDT_RESET_WIDTH] &= ~awc->ext_pulse_width_mask;
200 s->regs[WDT_RESET_WIDTH] |= data & awc->ext_pulse_width_mask;
201 break;
202
203 case WDT_RESET_MASK1:
204 /* TODO: implement */
205 s->regs[WDT_RESET_MASK1] = data;
206 break;
207
208 case WDT_TIMEOUT_STATUS:
209 case WDT_TIMEOUT_CLEAR:
210 case WDT_RESET_MASK2:
211 case WDT_SW_RESET_MASK1:
212 case WDT_SW_RESET_MASK2:
213 qemu_log_mask(LOG_UNIMP,
214 "%s: uninmplemented write at offset 0x%" HWADDR_PRIx "\n",
215 __func__, offset);
216 break;
217 case WDT_SW_RESET_CTRL:
218 if (aspeed_wdt_is_soc_reset_mode(s) &&
219 (data == WDT_SW_RESET_ENABLE)) {
220 watchdog_perform_action();
221 }
222 break;
223 default:
224 qemu_log_mask(LOG_GUEST_ERROR,
225 "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
226 __func__, offset);
227 }
228 }
229
230 static const VMStateDescription vmstate_aspeed_wdt = {
231 .name = "vmstate_aspeed_wdt",
232 .version_id = 0,
233 .minimum_version_id = 0,
234 .fields = (const VMStateField[]) {
235 VMSTATE_TIMER_PTR(timer, AspeedWDTState),
236 VMSTATE_UINT32_ARRAY(regs, AspeedWDTState, ASPEED_WDT_REGS_MAX),
237 VMSTATE_END_OF_LIST()
238 }
239 };
240
241 static const MemoryRegionOps aspeed_wdt_ops = {
242 .read = aspeed_wdt_read,
243 .write = aspeed_wdt_write,
244 .endianness = DEVICE_LITTLE_ENDIAN,
245 .valid.min_access_size = 4,
246 .valid.max_access_size = 4,
247 .valid.unaligned = false,
248 };
249
aspeed_wdt_reset(DeviceState * dev)250 static void aspeed_wdt_reset(DeviceState *dev)
251 {
252 AspeedWDTState *s = ASPEED_WDT(dev);
253 AspeedWDTClass *awc = ASPEED_WDT_GET_CLASS(s);
254
255 s->regs[WDT_STATUS] = awc->default_status;
256 s->regs[WDT_RELOAD_VALUE] = awc->default_reload_value;
257 s->regs[WDT_RESTART] = 0;
258 s->regs[WDT_CTRL] = awc->sanitize_ctrl(0);
259 s->regs[WDT_RESET_WIDTH] = 0xFF;
260
261 timer_del(s->timer);
262 }
263
aspeed_wdt_timer_expired(void * dev)264 static void aspeed_wdt_timer_expired(void *dev)
265 {
266 AspeedWDTState *s = ASPEED_WDT(dev);
267 uint32_t reset_ctrl_reg = ASPEED_WDT_GET_CLASS(s)->reset_ctrl_reg;
268
269 /* Do not reset on SDRAM controller reset */
270 if (s->scu->regs[reset_ctrl_reg] & SCU_RESET_SDRAM) {
271 timer_del(s->timer);
272 s->regs[WDT_CTRL] = 0;
273 return;
274 }
275
276 qemu_log_mask(CPU_LOG_RESET, "Watchdog timer %" HWADDR_PRIx " expired.\n",
277 s->iomem.addr);
278 watchdog_perform_action();
279 timer_del(s->timer);
280 }
281
282 #define PCLK_HZ 24000000
283
aspeed_wdt_realize(DeviceState * dev,Error ** errp)284 static void aspeed_wdt_realize(DeviceState *dev, Error **errp)
285 {
286 SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
287 AspeedWDTState *s = ASPEED_WDT(dev);
288 AspeedWDTClass *awc = ASPEED_WDT_GET_CLASS(dev);
289
290 assert(s->scu);
291
292 s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, aspeed_wdt_timer_expired, dev);
293
294 /*
295 * FIXME: This setting should be derived from the SCU hw strapping
296 * register SCU70
297 */
298 s->pclk_freq = PCLK_HZ;
299
300 memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_wdt_ops, s,
301 TYPE_ASPEED_WDT, awc->iosize);
302 sysbus_init_mmio(sbd, &s->iomem);
303 }
304
305 static const Property aspeed_wdt_properties[] = {
306 DEFINE_PROP_LINK("scu", AspeedWDTState, scu, TYPE_ASPEED_SCU,
307 AspeedSCUState *),
308 };
309
aspeed_wdt_class_init(ObjectClass * klass,const void * data)310 static void aspeed_wdt_class_init(ObjectClass *klass, const void *data)
311 {
312 DeviceClass *dc = DEVICE_CLASS(klass);
313
314 dc->desc = "ASPEED Watchdog Controller";
315 dc->realize = aspeed_wdt_realize;
316 device_class_set_legacy_reset(dc, aspeed_wdt_reset);
317 set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
318 dc->vmsd = &vmstate_aspeed_wdt;
319 device_class_set_props(dc, aspeed_wdt_properties);
320 dc->desc = "Aspeed watchdog device";
321 }
322
323 static const TypeInfo aspeed_wdt_info = {
324 .parent = TYPE_SYS_BUS_DEVICE,
325 .name = TYPE_ASPEED_WDT,
326 .instance_size = sizeof(AspeedWDTState),
327 .class_init = aspeed_wdt_class_init,
328 .class_size = sizeof(AspeedWDTClass),
329 .abstract = true,
330 };
331
aspeed_2400_wdt_class_init(ObjectClass * klass,const void * data)332 static void aspeed_2400_wdt_class_init(ObjectClass *klass, const void *data)
333 {
334 DeviceClass *dc = DEVICE_CLASS(klass);
335 AspeedWDTClass *awc = ASPEED_WDT_CLASS(klass);
336
337 dc->desc = "ASPEED 2400 Watchdog Controller";
338 awc->iosize = 0x20;
339 awc->ext_pulse_width_mask = 0xff;
340 awc->reset_ctrl_reg = SCU_RESET_CONTROL1;
341 awc->wdt_reload = aspeed_wdt_reload;
342 awc->sanitize_ctrl = aspeed_2400_sanitize_ctrl;
343 awc->default_status = 0x03EF1480;
344 awc->default_reload_value = 0x03EF1480;
345 }
346
347 static const TypeInfo aspeed_2400_wdt_info = {
348 .name = TYPE_ASPEED_2400_WDT,
349 .parent = TYPE_ASPEED_WDT,
350 .instance_size = sizeof(AspeedWDTState),
351 .class_init = aspeed_2400_wdt_class_init,
352 };
353
aspeed_2500_wdt_reset_pulse(AspeedWDTState * s,uint32_t property)354 static void aspeed_2500_wdt_reset_pulse(AspeedWDTState *s, uint32_t property)
355 {
356 if (property) {
357 if (property == WDT_ACTIVE_HIGH_MAGIC) {
358 s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_ACTIVE_HIGH;
359 } else if (property == WDT_ACTIVE_LOW_MAGIC) {
360 s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_ACTIVE_HIGH;
361 } else if (property == WDT_PUSH_PULL_MAGIC) {
362 s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_PUSH_PULL;
363 } else if (property == WDT_OPEN_DRAIN_MAGIC) {
364 s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_PUSH_PULL;
365 }
366 }
367 }
368
aspeed_2500_wdt_class_init(ObjectClass * klass,const void * data)369 static void aspeed_2500_wdt_class_init(ObjectClass *klass, const void *data)
370 {
371 DeviceClass *dc = DEVICE_CLASS(klass);
372 AspeedWDTClass *awc = ASPEED_WDT_CLASS(klass);
373
374 dc->desc = "ASPEED 2500 Watchdog Controller";
375 awc->iosize = 0x20;
376 awc->ext_pulse_width_mask = 0xfffff;
377 awc->reset_ctrl_reg = SCU_RESET_CONTROL1;
378 awc->reset_pulse = aspeed_2500_wdt_reset_pulse;
379 awc->wdt_reload = aspeed_wdt_reload_1mhz;
380 awc->sanitize_ctrl = aspeed_2500_sanitize_ctrl;
381 awc->default_status = 0x014FB180;
382 awc->default_reload_value = 0x014FB180;
383 }
384
385 static const TypeInfo aspeed_2500_wdt_info = {
386 .name = TYPE_ASPEED_2500_WDT,
387 .parent = TYPE_ASPEED_WDT,
388 .instance_size = sizeof(AspeedWDTState),
389 .class_init = aspeed_2500_wdt_class_init,
390 };
391
aspeed_2600_wdt_class_init(ObjectClass * klass,const void * data)392 static void aspeed_2600_wdt_class_init(ObjectClass *klass, const void *data)
393 {
394 DeviceClass *dc = DEVICE_CLASS(klass);
395 AspeedWDTClass *awc = ASPEED_WDT_CLASS(klass);
396
397 dc->desc = "ASPEED 2600 Watchdog Controller";
398 awc->iosize = 0x40;
399 awc->ext_pulse_width_mask = 0xfffff; /* TODO */
400 awc->reset_ctrl_reg = AST2600_SCU_RESET_CONTROL1;
401 awc->reset_pulse = aspeed_2500_wdt_reset_pulse;
402 awc->wdt_reload = aspeed_wdt_reload_1mhz;
403 awc->sanitize_ctrl = aspeed_2600_sanitize_ctrl;
404 awc->default_status = 0x014FB180;
405 awc->default_reload_value = 0x014FB180;
406 }
407
408 static const TypeInfo aspeed_2600_wdt_info = {
409 .name = TYPE_ASPEED_2600_WDT,
410 .parent = TYPE_ASPEED_WDT,
411 .instance_size = sizeof(AspeedWDTState),
412 .class_init = aspeed_2600_wdt_class_init,
413 };
414
aspeed_1030_wdt_class_init(ObjectClass * klass,const void * data)415 static void aspeed_1030_wdt_class_init(ObjectClass *klass, const void *data)
416 {
417 DeviceClass *dc = DEVICE_CLASS(klass);
418 AspeedWDTClass *awc = ASPEED_WDT_CLASS(klass);
419
420 dc->desc = "ASPEED 1030 Watchdog Controller";
421 awc->iosize = 0x80;
422 awc->ext_pulse_width_mask = 0xfffff; /* TODO */
423 awc->reset_ctrl_reg = AST2600_SCU_RESET_CONTROL1;
424 awc->reset_pulse = aspeed_2500_wdt_reset_pulse;
425 awc->wdt_reload = aspeed_wdt_reload_1mhz;
426 awc->sanitize_ctrl = aspeed_2600_sanitize_ctrl;
427 awc->default_status = 0x014FB180;
428 awc->default_reload_value = 0x014FB180;
429 }
430
431 static const TypeInfo aspeed_1030_wdt_info = {
432 .name = TYPE_ASPEED_1030_WDT,
433 .parent = TYPE_ASPEED_WDT,
434 .instance_size = sizeof(AspeedWDTState),
435 .class_init = aspeed_1030_wdt_class_init,
436 };
437
aspeed_2700_wdt_class_init(ObjectClass * klass,const void * data)438 static void aspeed_2700_wdt_class_init(ObjectClass *klass, const void *data)
439 {
440 DeviceClass *dc = DEVICE_CLASS(klass);
441 AspeedWDTClass *awc = ASPEED_WDT_CLASS(klass);
442
443 dc->desc = "ASPEED 2700 Watchdog Controller";
444 awc->iosize = 0x80;
445 awc->ext_pulse_width_mask = 0xfffff; /* TODO */
446 awc->reset_ctrl_reg = AST2600_SCU_RESET_CONTROL1;
447 awc->reset_pulse = aspeed_2500_wdt_reset_pulse;
448 awc->wdt_reload = aspeed_wdt_reload_1mhz;
449 awc->sanitize_ctrl = aspeed_2600_sanitize_ctrl;
450 awc->default_status = 0x014FB180;
451 awc->default_reload_value = 0x014FB180;
452 }
453
454 static const TypeInfo aspeed_2700_wdt_info = {
455 .name = TYPE_ASPEED_2700_WDT,
456 .parent = TYPE_ASPEED_WDT,
457 .instance_size = sizeof(AspeedWDTState),
458 .class_init = aspeed_2700_wdt_class_init,
459 };
460
wdt_aspeed_register_types(void)461 static void wdt_aspeed_register_types(void)
462 {
463 type_register_static(&aspeed_wdt_info);
464 type_register_static(&aspeed_2400_wdt_info);
465 type_register_static(&aspeed_2500_wdt_info);
466 type_register_static(&aspeed_2600_wdt_info);
467 type_register_static(&aspeed_2700_wdt_info);
468 type_register_static(&aspeed_1030_wdt_info);
469 }
470
471 type_init(wdt_aspeed_register_types)
472