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