xref: /openbmc/qemu/hw/watchdog/wdt_imx2.c (revision 7d87775f)
1 /*
2  * Copyright (c) 2018, Impinj, Inc.
3  *
4  * i.MX2 Watchdog IP block
5  *
6  * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
7  *
8  * This work is licensed under the terms of the GNU GPL, version 2 or later.
9  * See the COPYING file in the top-level directory.
10  */
11 
12 #include "qemu/osdep.h"
13 #include "qemu/bitops.h"
14 #include "qemu/module.h"
15 #include "sysemu/watchdog.h"
16 #include "migration/vmstate.h"
17 #include "hw/qdev-properties.h"
18 
19 #include "hw/watchdog/wdt_imx2.h"
20 #include "trace.h"
21 
22 static void imx2_wdt_interrupt(void *opaque)
23 {
24     IMX2WdtState *s = IMX2_WDT(opaque);
25 
26     trace_imx2_wdt_interrupt();
27 
28     s->wicr |= IMX2_WDT_WICR_WTIS;
29     qemu_set_irq(s->irq, 1);
30 }
31 
32 static void imx2_wdt_expired(void *opaque)
33 {
34     IMX2WdtState *s = IMX2_WDT(opaque);
35 
36     trace_imx2_wdt_expired();
37 
38     s->wrsr = IMX2_WDT_WRSR_TOUT;
39 
40     /* Perform watchdog action if watchdog is enabled */
41     if (s->wcr & IMX2_WDT_WCR_WDE) {
42         watchdog_perform_action();
43     }
44 }
45 
46 static void imx2_wdt_reset(DeviceState *dev)
47 {
48     IMX2WdtState *s = IMX2_WDT(dev);
49 
50     ptimer_transaction_begin(s->timer);
51     ptimer_stop(s->timer);
52     ptimer_transaction_commit(s->timer);
53 
54     if (s->pretimeout_support) {
55         ptimer_transaction_begin(s->itimer);
56         ptimer_stop(s->itimer);
57         ptimer_transaction_commit(s->itimer);
58     }
59 
60     s->wicr_locked = false;
61     s->wcr_locked = false;
62     s->wcr_wde_locked = false;
63 
64     s->wcr = IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS;
65     s->wsr = 0;
66     s->wrsr &= ~(IMX2_WDT_WRSR_TOUT | IMX2_WDT_WRSR_SFTW);
67     s->wicr = IMX2_WDT_WICR_WICT_DEF;
68     s->wmcr = IMX2_WDT_WMCR_PDE;
69 }
70 
71 static uint64_t imx2_wdt_read(void *opaque, hwaddr addr, unsigned int size)
72 {
73     IMX2WdtState *s = IMX2_WDT(opaque);
74     uint16_t value = 0;
75 
76     switch (addr) {
77     case IMX2_WDT_WCR:
78         value = s->wcr;
79         break;
80     case IMX2_WDT_WSR:
81         value = s->wsr;
82         break;
83     case IMX2_WDT_WRSR:
84         value = s->wrsr;
85         break;
86     case IMX2_WDT_WICR:
87         value = s->wicr;
88         break;
89     case IMX2_WDT_WMCR:
90         value = s->wmcr;
91         break;
92     }
93 
94     trace_imx2_wdt_read(addr, value);
95 
96     return value;
97 }
98 
99 static void imx_wdt2_update_itimer(IMX2WdtState *s, bool start)
100 {
101     bool running = (s->wcr & IMX2_WDT_WCR_WDE) && (s->wcr & IMX2_WDT_WCR_WT);
102     bool enabled = s->wicr & IMX2_WDT_WICR_WIE;
103 
104     ptimer_transaction_begin(s->itimer);
105     if (start || !enabled) {
106         ptimer_stop(s->itimer);
107     }
108     if (running && enabled) {
109         int count = ptimer_get_count(s->timer);
110         int pretimeout = s->wicr & IMX2_WDT_WICR_WICT;
111 
112         /*
113          * Only (re-)start pretimeout timer if its counter value is larger
114          * than 0. Otherwise it will fire right away and we'll get an
115          * interrupt loop.
116          */
117         if (count > pretimeout) {
118             ptimer_set_count(s->itimer, count - pretimeout);
119             if (start) {
120                 ptimer_run(s->itimer, 1);
121             }
122         }
123     }
124     ptimer_transaction_commit(s->itimer);
125 }
126 
127 static void imx_wdt2_update_timer(IMX2WdtState *s, bool start)
128 {
129     ptimer_transaction_begin(s->timer);
130     if (start) {
131         ptimer_stop(s->timer);
132     }
133     if ((s->wcr & IMX2_WDT_WCR_WDE) && (s->wcr & IMX2_WDT_WCR_WT)) {
134         int count = (s->wcr & IMX2_WDT_WCR_WT) >> 8;
135 
136         /* A value of 0 reflects one period (0.5s). */
137         ptimer_set_count(s->timer, count + 1);
138         if (start) {
139             ptimer_run(s->timer, 1);
140         }
141     }
142     ptimer_transaction_commit(s->timer);
143     if (s->pretimeout_support) {
144         imx_wdt2_update_itimer(s, start);
145     }
146 }
147 
148 static void imx2_wdt_write(void *opaque, hwaddr addr,
149                            uint64_t value, unsigned int size)
150 {
151     IMX2WdtState *s = IMX2_WDT(opaque);
152 
153     trace_imx2_wdt_write(addr, value);
154 
155     switch (addr) {
156     case IMX2_WDT_WCR:
157         if (s->wcr_locked) {
158             value &= ~IMX2_WDT_WCR_LOCK_MASK;
159             value |= (s->wicr & IMX2_WDT_WCR_LOCK_MASK);
160         }
161         s->wcr_locked = true;
162         if (s->wcr_wde_locked) {
163             value &= ~IMX2_WDT_WCR_WDE;
164             value |= (s->wicr & ~IMX2_WDT_WCR_WDE);
165         } else if (value & IMX2_WDT_WCR_WDE) {
166             s->wcr_wde_locked = true;
167         }
168         if (s->wcr_wdt_locked) {
169             value &= ~IMX2_WDT_WCR_WDT;
170             value |= (s->wicr & ~IMX2_WDT_WCR_WDT);
171         } else if (value & IMX2_WDT_WCR_WDT) {
172             s->wcr_wdt_locked = true;
173         }
174 
175         s->wcr = value;
176         if (!(value & IMX2_WDT_WCR_SRS)) {
177             s->wrsr = IMX2_WDT_WRSR_SFTW;
178         }
179         if (!(value & (IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS)) ||
180             (!(value & IMX2_WDT_WCR_WT) && (value & IMX2_WDT_WCR_WDE))) {
181             watchdog_perform_action();
182         }
183         s->wcr |= IMX2_WDT_WCR_SRS;
184         imx_wdt2_update_timer(s, true);
185         break;
186     case IMX2_WDT_WSR:
187         if (s->wsr == IMX2_WDT_SEQ1 && value == IMX2_WDT_SEQ2) {
188             imx_wdt2_update_timer(s, false);
189         }
190         s->wsr = value;
191         break;
192     case IMX2_WDT_WRSR:
193         break;
194     case IMX2_WDT_WICR:
195         if (!s->pretimeout_support) {
196             return;
197         }
198         value &= IMX2_WDT_WICR_LOCK_MASK | IMX2_WDT_WICR_WTIS;
199         if (s->wicr_locked) {
200             value &= IMX2_WDT_WICR_WTIS;
201             value |= (s->wicr & IMX2_WDT_WICR_LOCK_MASK);
202         }
203         s->wicr = value | (s->wicr & IMX2_WDT_WICR_WTIS);
204         if (value & IMX2_WDT_WICR_WTIS) {
205             s->wicr &= ~IMX2_WDT_WICR_WTIS;
206             qemu_set_irq(s->irq, 0);
207         }
208         imx_wdt2_update_itimer(s, true);
209         s->wicr_locked = true;
210         break;
211     case IMX2_WDT_WMCR:
212         s->wmcr = value & IMX2_WDT_WMCR_PDE;
213         break;
214     }
215 }
216 
217 static const MemoryRegionOps imx2_wdt_ops = {
218     .read  = imx2_wdt_read,
219     .write = imx2_wdt_write,
220     .endianness = DEVICE_NATIVE_ENDIAN,
221     .impl = {
222         /*
223          * Our device would not work correctly if the guest was doing
224          * unaligned access. This might not be a limitation on the
225          * real device but in practice there is no reason for a guest
226          * to access this device unaligned.
227          */
228         .min_access_size = 2,
229         .max_access_size = 2,
230         .unaligned = false,
231     },
232 };
233 
234 static const VMStateDescription vmstate_imx2_wdt = {
235     .name = "imx2.wdt",
236     .fields = (const VMStateField[]) {
237         VMSTATE_PTIMER(timer, IMX2WdtState),
238         VMSTATE_PTIMER(itimer, IMX2WdtState),
239         VMSTATE_BOOL(wicr_locked, IMX2WdtState),
240         VMSTATE_BOOL(wcr_locked, IMX2WdtState),
241         VMSTATE_BOOL(wcr_wde_locked, IMX2WdtState),
242         VMSTATE_BOOL(wcr_wdt_locked, IMX2WdtState),
243         VMSTATE_UINT16(wcr, IMX2WdtState),
244         VMSTATE_UINT16(wsr, IMX2WdtState),
245         VMSTATE_UINT16(wrsr, IMX2WdtState),
246         VMSTATE_UINT16(wmcr, IMX2WdtState),
247         VMSTATE_UINT16(wicr, IMX2WdtState),
248         VMSTATE_END_OF_LIST()
249     }
250 };
251 
252 static void imx2_wdt_realize(DeviceState *dev, Error **errp)
253 {
254     IMX2WdtState *s = IMX2_WDT(dev);
255     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
256 
257     memory_region_init_io(&s->mmio, OBJECT(dev),
258                           &imx2_wdt_ops, s,
259                           TYPE_IMX2_WDT,
260                           IMX2_WDT_MMIO_SIZE);
261     sysbus_init_mmio(sbd, &s->mmio);
262     sysbus_init_irq(sbd, &s->irq);
263 
264     s->timer = ptimer_init(imx2_wdt_expired, s,
265                            PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
266                            PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
267                            PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
268     ptimer_transaction_begin(s->timer);
269     ptimer_set_freq(s->timer, 2);
270     ptimer_set_limit(s->timer, 0xff, 1);
271     ptimer_transaction_commit(s->timer);
272     if (s->pretimeout_support) {
273         s->itimer = ptimer_init(imx2_wdt_interrupt, s,
274                                 PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
275                                 PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
276                                 PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
277         ptimer_transaction_begin(s->itimer);
278         ptimer_set_freq(s->itimer, 2);
279         ptimer_set_limit(s->itimer, 0xff, 1);
280         ptimer_transaction_commit(s->itimer);
281     }
282 }
283 
284 static Property imx2_wdt_properties[] = {
285     DEFINE_PROP_BOOL("pretimeout-support", IMX2WdtState, pretimeout_support,
286                      false),
287     DEFINE_PROP_END_OF_LIST()
288 };
289 
290 static void imx2_wdt_class_init(ObjectClass *klass, void *data)
291 {
292     DeviceClass *dc = DEVICE_CLASS(klass);
293 
294     device_class_set_props(dc, imx2_wdt_properties);
295     dc->realize = imx2_wdt_realize;
296     device_class_set_legacy_reset(dc, imx2_wdt_reset);
297     dc->vmsd = &vmstate_imx2_wdt;
298     dc->desc = "i.MX2 watchdog timer";
299     set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
300 }
301 
302 static const TypeInfo imx2_wdt_info = {
303     .name          = TYPE_IMX2_WDT,
304     .parent        = TYPE_SYS_BUS_DEVICE,
305     .instance_size = sizeof(IMX2WdtState),
306     .class_init    = imx2_wdt_class_init,
307 };
308 
309 static void imx2_wdt_register_type(void)
310 {
311     type_register_static(&imx2_wdt_info);
312 }
313 type_init(imx2_wdt_register_type)
314