xref: /qemu/hw/watchdog/wdt_aspeed.c (revision f55d613bc97cd8d08487eddec313c3298a906a91)
1854123bfSCédric Le Goater /*
2854123bfSCédric Le Goater  * ASPEED Watchdog Controller
3854123bfSCédric Le Goater  *
4854123bfSCédric Le Goater  * Copyright (C) 2016-2017 IBM Corp.
5854123bfSCédric Le Goater  *
6854123bfSCédric Le Goater  * This code is licensed under the GPL version 2 or later. See the
7854123bfSCédric Le Goater  * COPYING file in the top-level directory.
8854123bfSCédric Le Goater  */
9854123bfSCédric Le Goater 
10854123bfSCédric Le Goater #include "qemu/osdep.h"
11*f55d613bSAndrew Jeffery 
12*f55d613bSAndrew Jeffery #include "qapi/error.h"
13854123bfSCédric Le Goater #include "qemu/log.h"
14854123bfSCédric Le Goater #include "qemu/timer.h"
15*f55d613bSAndrew Jeffery #include "sysemu/watchdog.h"
16*f55d613bSAndrew Jeffery #include "hw/misc/aspeed_scu.h"
17*f55d613bSAndrew Jeffery #include "hw/sysbus.h"
18854123bfSCédric Le Goater #include "hw/watchdog/wdt_aspeed.h"
19854123bfSCédric Le Goater 
20854123bfSCédric Le Goater #define WDT_STATUS                      (0x00 / 4)
21854123bfSCédric Le Goater #define WDT_RELOAD_VALUE                (0x04 / 4)
22854123bfSCédric Le Goater #define WDT_RESTART                     (0x08 / 4)
23854123bfSCédric Le Goater #define WDT_CTRL                        (0x0C / 4)
24854123bfSCédric Le Goater #define   WDT_CTRL_RESET_MODE_SOC       (0x00 << 5)
25854123bfSCédric Le Goater #define   WDT_CTRL_RESET_MODE_FULL_CHIP (0x01 << 5)
26854123bfSCédric Le Goater #define   WDT_CTRL_1MHZ_CLK             BIT(4)
27854123bfSCédric Le Goater #define   WDT_CTRL_WDT_EXT              BIT(3)
28854123bfSCédric Le Goater #define   WDT_CTRL_WDT_INTR             BIT(2)
29854123bfSCédric Le Goater #define   WDT_CTRL_RESET_SYSTEM         BIT(1)
30854123bfSCédric Le Goater #define   WDT_CTRL_ENABLE               BIT(0)
31*f55d613bSAndrew Jeffery #define WDT_RESET_WIDTH                 (0x18 / 4)
32*f55d613bSAndrew Jeffery #define   WDT_RESET_WIDTH_ACTIVE_HIGH   BIT(31)
33*f55d613bSAndrew Jeffery #define     WDT_POLARITY_MASK           (0xFF << 24)
34*f55d613bSAndrew Jeffery #define     WDT_ACTIVE_HIGH_MAGIC       (0xA5 << 24)
35*f55d613bSAndrew Jeffery #define     WDT_ACTIVE_LOW_MAGIC        (0x5A << 24)
36*f55d613bSAndrew Jeffery #define   WDT_RESET_WIDTH_PUSH_PULL     BIT(30)
37*f55d613bSAndrew Jeffery #define     WDT_DRIVE_TYPE_MASK         (0xFF << 24)
38*f55d613bSAndrew Jeffery #define     WDT_PUSH_PULL_MAGIC         (0xA8 << 24)
39*f55d613bSAndrew Jeffery #define     WDT_OPEN_DRAIN_MAGIC        (0x8A << 24)
40854123bfSCédric Le Goater 
41854123bfSCédric Le Goater #define WDT_TIMEOUT_STATUS              (0x10 / 4)
42854123bfSCédric Le Goater #define WDT_TIMEOUT_CLEAR               (0x14 / 4)
43854123bfSCédric Le Goater 
44854123bfSCédric Le Goater #define WDT_RESTART_MAGIC               0x4755
45854123bfSCédric Le Goater 
46854123bfSCédric Le Goater static bool aspeed_wdt_is_enabled(const AspeedWDTState *s)
47854123bfSCédric Le Goater {
48854123bfSCédric Le Goater     return s->regs[WDT_CTRL] & WDT_CTRL_ENABLE;
49854123bfSCédric Le Goater }
50854123bfSCédric Le Goater 
51*f55d613bSAndrew Jeffery static bool is_ast2500(const AspeedWDTState *s)
52*f55d613bSAndrew Jeffery {
53*f55d613bSAndrew Jeffery     switch (s->silicon_rev) {
54*f55d613bSAndrew Jeffery     case AST2500_A0_SILICON_REV:
55*f55d613bSAndrew Jeffery     case AST2500_A1_SILICON_REV:
56*f55d613bSAndrew Jeffery         return true;
57*f55d613bSAndrew Jeffery     case AST2400_A0_SILICON_REV:
58*f55d613bSAndrew Jeffery     case AST2400_A1_SILICON_REV:
59*f55d613bSAndrew Jeffery     default:
60*f55d613bSAndrew Jeffery         break;
61*f55d613bSAndrew Jeffery     }
62*f55d613bSAndrew Jeffery 
63*f55d613bSAndrew Jeffery     return false;
64*f55d613bSAndrew Jeffery }
65*f55d613bSAndrew Jeffery 
66854123bfSCédric Le Goater static uint64_t aspeed_wdt_read(void *opaque, hwaddr offset, unsigned size)
67854123bfSCédric Le Goater {
68854123bfSCédric Le Goater     AspeedWDTState *s = ASPEED_WDT(opaque);
69854123bfSCédric Le Goater 
70854123bfSCédric Le Goater     offset >>= 2;
71854123bfSCédric Le Goater 
72854123bfSCédric Le Goater     switch (offset) {
73854123bfSCédric Le Goater     case WDT_STATUS:
74854123bfSCédric Le Goater         return s->regs[WDT_STATUS];
75854123bfSCédric Le Goater     case WDT_RELOAD_VALUE:
76854123bfSCédric Le Goater         return s->regs[WDT_RELOAD_VALUE];
77854123bfSCédric Le Goater     case WDT_RESTART:
78854123bfSCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR,
79854123bfSCédric Le Goater                       "%s: read from write-only reg at offset 0x%"
80854123bfSCédric Le Goater                       HWADDR_PRIx "\n", __func__, offset);
81854123bfSCédric Le Goater         return 0;
82854123bfSCédric Le Goater     case WDT_CTRL:
83854123bfSCédric Le Goater         return s->regs[WDT_CTRL];
84*f55d613bSAndrew Jeffery     case WDT_RESET_WIDTH:
85*f55d613bSAndrew Jeffery         return s->regs[WDT_RESET_WIDTH];
86854123bfSCédric Le Goater     case WDT_TIMEOUT_STATUS:
87854123bfSCédric Le Goater     case WDT_TIMEOUT_CLEAR:
88854123bfSCédric Le Goater         qemu_log_mask(LOG_UNIMP,
89854123bfSCédric Le Goater                       "%s: uninmplemented read at offset 0x%" HWADDR_PRIx "\n",
90854123bfSCédric Le Goater                       __func__, offset);
91854123bfSCédric Le Goater         return 0;
92854123bfSCédric Le Goater     default:
93854123bfSCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR,
94854123bfSCédric Le Goater                       "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n",
95854123bfSCédric Le Goater                       __func__, offset);
96854123bfSCédric Le Goater         return 0;
97854123bfSCédric Le Goater     }
98854123bfSCédric Le Goater 
99854123bfSCédric Le Goater }
100854123bfSCédric Le Goater 
101854123bfSCédric Le Goater static void aspeed_wdt_reload(AspeedWDTState *s, bool pclk)
102854123bfSCédric Le Goater {
103854123bfSCédric Le Goater     uint32_t reload;
104854123bfSCédric Le Goater 
105854123bfSCédric Le Goater     if (pclk) {
106854123bfSCédric Le Goater         reload = muldiv64(s->regs[WDT_RELOAD_VALUE], NANOSECONDS_PER_SECOND,
107854123bfSCédric Le Goater                           s->pclk_freq);
108854123bfSCédric Le Goater     } else {
109854123bfSCédric Le Goater         reload = s->regs[WDT_RELOAD_VALUE] * 1000;
110854123bfSCédric Le Goater     }
111854123bfSCédric Le Goater 
112854123bfSCédric Le Goater     if (aspeed_wdt_is_enabled(s)) {
113854123bfSCédric Le Goater         timer_mod(s->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + reload);
114854123bfSCédric Le Goater     }
115854123bfSCédric Le Goater }
116854123bfSCédric Le Goater 
117854123bfSCédric Le Goater static void aspeed_wdt_write(void *opaque, hwaddr offset, uint64_t data,
118854123bfSCédric Le Goater                              unsigned size)
119854123bfSCédric Le Goater {
120854123bfSCédric Le Goater     AspeedWDTState *s = ASPEED_WDT(opaque);
121854123bfSCédric Le Goater     bool enable = data & WDT_CTRL_ENABLE;
122854123bfSCédric Le Goater 
123854123bfSCédric Le Goater     offset >>= 2;
124854123bfSCédric Le Goater 
125854123bfSCédric Le Goater     switch (offset) {
126854123bfSCédric Le Goater     case WDT_STATUS:
127854123bfSCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR,
128854123bfSCédric Le Goater                       "%s: write to read-only reg at offset 0x%"
129854123bfSCédric Le Goater                       HWADDR_PRIx "\n", __func__, offset);
130854123bfSCédric Le Goater         break;
131854123bfSCédric Le Goater     case WDT_RELOAD_VALUE:
132854123bfSCédric Le Goater         s->regs[WDT_RELOAD_VALUE] = data;
133854123bfSCédric Le Goater         break;
134854123bfSCédric Le Goater     case WDT_RESTART:
135854123bfSCédric Le Goater         if ((data & 0xFFFF) == WDT_RESTART_MAGIC) {
136854123bfSCédric Le Goater             s->regs[WDT_STATUS] = s->regs[WDT_RELOAD_VALUE];
137854123bfSCédric Le Goater             aspeed_wdt_reload(s, !(data & WDT_CTRL_1MHZ_CLK));
138854123bfSCédric Le Goater         }
139854123bfSCédric Le Goater         break;
140854123bfSCédric Le Goater     case WDT_CTRL:
141854123bfSCédric Le Goater         if (enable && !aspeed_wdt_is_enabled(s)) {
142854123bfSCédric Le Goater             s->regs[WDT_CTRL] = data;
143854123bfSCédric Le Goater             aspeed_wdt_reload(s, !(data & WDT_CTRL_1MHZ_CLK));
144854123bfSCédric Le Goater         } else if (!enable && aspeed_wdt_is_enabled(s)) {
145854123bfSCédric Le Goater             s->regs[WDT_CTRL] = data;
146854123bfSCédric Le Goater             timer_del(s->timer);
147854123bfSCédric Le Goater         }
148854123bfSCédric Le Goater         break;
149*f55d613bSAndrew Jeffery     case WDT_RESET_WIDTH:
150*f55d613bSAndrew Jeffery     {
151*f55d613bSAndrew Jeffery         uint32_t property = data & WDT_POLARITY_MASK;
152*f55d613bSAndrew Jeffery 
153*f55d613bSAndrew Jeffery         if (property && is_ast2500(s)) {
154*f55d613bSAndrew Jeffery             if (property == WDT_ACTIVE_HIGH_MAGIC) {
155*f55d613bSAndrew Jeffery                 s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_ACTIVE_HIGH;
156*f55d613bSAndrew Jeffery             } else if (property == WDT_ACTIVE_LOW_MAGIC) {
157*f55d613bSAndrew Jeffery                 s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_ACTIVE_HIGH;
158*f55d613bSAndrew Jeffery             } else if (property == WDT_PUSH_PULL_MAGIC) {
159*f55d613bSAndrew Jeffery                 s->regs[WDT_RESET_WIDTH] |= WDT_RESET_WIDTH_PUSH_PULL;
160*f55d613bSAndrew Jeffery             } else if (property == WDT_OPEN_DRAIN_MAGIC) {
161*f55d613bSAndrew Jeffery                 s->regs[WDT_RESET_WIDTH] &= ~WDT_RESET_WIDTH_PUSH_PULL;
162*f55d613bSAndrew Jeffery             }
163*f55d613bSAndrew Jeffery         }
164*f55d613bSAndrew Jeffery         s->regs[WDT_RESET_WIDTH] &= ~s->ext_pulse_width_mask;
165*f55d613bSAndrew Jeffery         s->regs[WDT_RESET_WIDTH] |= data & s->ext_pulse_width_mask;
166*f55d613bSAndrew Jeffery         break;
167*f55d613bSAndrew Jeffery     }
168854123bfSCédric Le Goater     case WDT_TIMEOUT_STATUS:
169854123bfSCédric Le Goater     case WDT_TIMEOUT_CLEAR:
170854123bfSCédric Le Goater         qemu_log_mask(LOG_UNIMP,
171854123bfSCédric Le Goater                       "%s: uninmplemented write at offset 0x%" HWADDR_PRIx "\n",
172854123bfSCédric Le Goater                       __func__, offset);
173854123bfSCédric Le Goater         break;
174854123bfSCédric Le Goater     default:
175854123bfSCédric Le Goater         qemu_log_mask(LOG_GUEST_ERROR,
176854123bfSCédric Le Goater                       "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n",
177854123bfSCédric Le Goater                       __func__, offset);
178854123bfSCédric Le Goater     }
179854123bfSCédric Le Goater     return;
180854123bfSCédric Le Goater }
181854123bfSCédric Le Goater 
182854123bfSCédric Le Goater static WatchdogTimerModel model = {
183854123bfSCédric Le Goater     .wdt_name = TYPE_ASPEED_WDT,
184854123bfSCédric Le Goater     .wdt_description = "Aspeed watchdog device",
185854123bfSCédric Le Goater };
186854123bfSCédric Le Goater 
187854123bfSCédric Le Goater static const VMStateDescription vmstate_aspeed_wdt = {
188854123bfSCédric Le Goater     .name = "vmstate_aspeed_wdt",
189854123bfSCédric Le Goater     .version_id = 0,
190854123bfSCédric Le Goater     .minimum_version_id = 0,
191854123bfSCédric Le Goater     .fields = (VMStateField[]) {
192854123bfSCédric Le Goater         VMSTATE_TIMER_PTR(timer, AspeedWDTState),
193854123bfSCédric Le Goater         VMSTATE_UINT32_ARRAY(regs, AspeedWDTState, ASPEED_WDT_REGS_MAX),
194854123bfSCédric Le Goater         VMSTATE_END_OF_LIST()
195854123bfSCédric Le Goater     }
196854123bfSCédric Le Goater };
197854123bfSCédric Le Goater 
198854123bfSCédric Le Goater static const MemoryRegionOps aspeed_wdt_ops = {
199854123bfSCédric Le Goater     .read = aspeed_wdt_read,
200854123bfSCédric Le Goater     .write = aspeed_wdt_write,
201854123bfSCédric Le Goater     .endianness = DEVICE_LITTLE_ENDIAN,
202854123bfSCédric Le Goater     .valid.min_access_size = 4,
203854123bfSCédric Le Goater     .valid.max_access_size = 4,
204854123bfSCédric Le Goater     .valid.unaligned = false,
205854123bfSCédric Le Goater };
206854123bfSCédric Le Goater 
207854123bfSCédric Le Goater static void aspeed_wdt_reset(DeviceState *dev)
208854123bfSCédric Le Goater {
209854123bfSCédric Le Goater     AspeedWDTState *s = ASPEED_WDT(dev);
210854123bfSCédric Le Goater 
211854123bfSCédric Le Goater     s->regs[WDT_STATUS] = 0x3EF1480;
212854123bfSCédric Le Goater     s->regs[WDT_RELOAD_VALUE] = 0x03EF1480;
213854123bfSCédric Le Goater     s->regs[WDT_RESTART] = 0;
214854123bfSCédric Le Goater     s->regs[WDT_CTRL] = 0;
215*f55d613bSAndrew Jeffery     s->regs[WDT_RESET_WIDTH] = 0xFF;
216854123bfSCédric Le Goater 
217854123bfSCédric Le Goater     timer_del(s->timer);
218854123bfSCédric Le Goater }
219854123bfSCédric Le Goater 
220854123bfSCédric Le Goater static void aspeed_wdt_timer_expired(void *dev)
221854123bfSCédric Le Goater {
222854123bfSCédric Le Goater     AspeedWDTState *s = ASPEED_WDT(dev);
223854123bfSCédric Le Goater 
224854123bfSCédric Le Goater     qemu_log_mask(CPU_LOG_RESET, "Watchdog timer expired.\n");
225854123bfSCédric Le Goater     watchdog_perform_action();
226854123bfSCédric Le Goater     timer_del(s->timer);
227854123bfSCédric Le Goater }
228854123bfSCédric Le Goater 
229854123bfSCédric Le Goater #define PCLK_HZ 24000000
230854123bfSCédric Le Goater 
231854123bfSCédric Le Goater static void aspeed_wdt_realize(DeviceState *dev, Error **errp)
232854123bfSCédric Le Goater {
233854123bfSCédric Le Goater     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
234854123bfSCédric Le Goater     AspeedWDTState *s = ASPEED_WDT(dev);
235854123bfSCédric Le Goater 
236*f55d613bSAndrew Jeffery     if (!is_supported_silicon_rev(s->silicon_rev)) {
237*f55d613bSAndrew Jeffery         error_setg(errp, "Unknown silicon revision: 0x%" PRIx32,
238*f55d613bSAndrew Jeffery                 s->silicon_rev);
239*f55d613bSAndrew Jeffery         return;
240*f55d613bSAndrew Jeffery     }
241*f55d613bSAndrew Jeffery 
242*f55d613bSAndrew Jeffery     switch (s->silicon_rev) {
243*f55d613bSAndrew Jeffery     case AST2400_A0_SILICON_REV:
244*f55d613bSAndrew Jeffery     case AST2400_A1_SILICON_REV:
245*f55d613bSAndrew Jeffery         s->ext_pulse_width_mask = 0xff;
246*f55d613bSAndrew Jeffery         break;
247*f55d613bSAndrew Jeffery     case AST2500_A0_SILICON_REV:
248*f55d613bSAndrew Jeffery     case AST2500_A1_SILICON_REV:
249*f55d613bSAndrew Jeffery         s->ext_pulse_width_mask = 0xfffff;
250*f55d613bSAndrew Jeffery         break;
251*f55d613bSAndrew Jeffery     default:
252*f55d613bSAndrew Jeffery         g_assert_not_reached();
253*f55d613bSAndrew Jeffery     }
254*f55d613bSAndrew Jeffery 
255854123bfSCédric Le Goater     s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, aspeed_wdt_timer_expired, dev);
256854123bfSCédric Le Goater 
257854123bfSCédric Le Goater     /* FIXME: This setting should be derived from the SCU hw strapping
258854123bfSCédric Le Goater      * register SCU70
259854123bfSCédric Le Goater      */
260854123bfSCédric Le Goater     s->pclk_freq = PCLK_HZ;
261854123bfSCédric Le Goater 
262854123bfSCédric Le Goater     memory_region_init_io(&s->iomem, OBJECT(s), &aspeed_wdt_ops, s,
263854123bfSCédric Le Goater                           TYPE_ASPEED_WDT, ASPEED_WDT_REGS_MAX * 4);
264854123bfSCédric Le Goater     sysbus_init_mmio(sbd, &s->iomem);
265854123bfSCédric Le Goater }
266854123bfSCédric Le Goater 
267*f55d613bSAndrew Jeffery static Property aspeed_wdt_properties[] = {
268*f55d613bSAndrew Jeffery     DEFINE_PROP_UINT32("silicon-rev", AspeedWDTState, silicon_rev, 0),
269*f55d613bSAndrew Jeffery     DEFINE_PROP_END_OF_LIST(),
270*f55d613bSAndrew Jeffery };
271*f55d613bSAndrew Jeffery 
272854123bfSCédric Le Goater static void aspeed_wdt_class_init(ObjectClass *klass, void *data)
273854123bfSCédric Le Goater {
274854123bfSCédric Le Goater     DeviceClass *dc = DEVICE_CLASS(klass);
275854123bfSCédric Le Goater 
276854123bfSCédric Le Goater     dc->realize = aspeed_wdt_realize;
277854123bfSCédric Le Goater     dc->reset = aspeed_wdt_reset;
278854123bfSCédric Le Goater     set_bit(DEVICE_CATEGORY_MISC, dc->categories);
279854123bfSCédric Le Goater     dc->vmsd = &vmstate_aspeed_wdt;
280*f55d613bSAndrew Jeffery     dc->props = aspeed_wdt_properties;
281854123bfSCédric Le Goater }
282854123bfSCédric Le Goater 
283854123bfSCédric Le Goater static const TypeInfo aspeed_wdt_info = {
284854123bfSCédric Le Goater     .parent = TYPE_SYS_BUS_DEVICE,
285854123bfSCédric Le Goater     .name  = TYPE_ASPEED_WDT,
286854123bfSCédric Le Goater     .instance_size  = sizeof(AspeedWDTState),
287854123bfSCédric Le Goater     .class_init = aspeed_wdt_class_init,
288854123bfSCédric Le Goater };
289854123bfSCédric Le Goater 
290854123bfSCédric Le Goater static void wdt_aspeed_register_types(void)
291854123bfSCédric Le Goater {
292854123bfSCédric Le Goater     watchdog_add_model(&model);
293854123bfSCédric Le Goater     type_register_static(&aspeed_wdt_info);
294854123bfSCédric Le Goater }
295854123bfSCédric Le Goater 
296854123bfSCédric Le Goater type_init(wdt_aspeed_register_types)
297