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