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
imx2_wdt_interrupt(void * opaque)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
imx2_wdt_expired(void * opaque)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
imx2_wdt_reset(DeviceState * dev)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
imx2_wdt_read(void * opaque,hwaddr addr,unsigned int size)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
imx_wdt2_update_itimer(IMX2WdtState * s,bool start)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
imx_wdt2_update_timer(IMX2WdtState * s,bool start)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
imx2_wdt_write(void * opaque,hwaddr addr,uint64_t value,unsigned int size)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
imx2_wdt_realize(DeviceState * dev,Error ** errp)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
imx2_wdt_class_init(ObjectClass * klass,void * data)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
imx2_wdt_register_type(void)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