137f95959SGuenter Roeck /*
237f95959SGuenter Roeck * Copyright (c) 2018, Impinj, Inc.
337f95959SGuenter Roeck *
437f95959SGuenter Roeck * i.MX2 Watchdog IP block
537f95959SGuenter Roeck *
637f95959SGuenter Roeck * Author: Andrey Smirnov <andrew.smirnov@gmail.com>
737f95959SGuenter Roeck *
837f95959SGuenter Roeck * This work is licensed under the terms of the GNU GPL, version 2 or later.
937f95959SGuenter Roeck * See the COPYING file in the top-level directory.
1037f95959SGuenter Roeck */
1137f95959SGuenter Roeck
1237f95959SGuenter Roeck #include "qemu/osdep.h"
1337f95959SGuenter Roeck #include "qemu/bitops.h"
1437f95959SGuenter Roeck #include "qemu/module.h"
1537f95959SGuenter Roeck #include "sysemu/watchdog.h"
16daca13d4SGuenter Roeck #include "migration/vmstate.h"
17daca13d4SGuenter Roeck #include "hw/qdev-properties.h"
1837f95959SGuenter Roeck
1937f95959SGuenter Roeck #include "hw/watchdog/wdt_imx2.h"
2018736a21SBernhard Beschow #include "trace.h"
2137f95959SGuenter Roeck
imx2_wdt_interrupt(void * opaque)22daca13d4SGuenter Roeck static void imx2_wdt_interrupt(void *opaque)
2337f95959SGuenter Roeck {
24daca13d4SGuenter Roeck IMX2WdtState *s = IMX2_WDT(opaque);
25daca13d4SGuenter Roeck
2688a9973eSBernhard Beschow trace_imx2_wdt_interrupt();
2788a9973eSBernhard Beschow
28daca13d4SGuenter Roeck s->wicr |= IMX2_WDT_WICR_WTIS;
29daca13d4SGuenter Roeck qemu_set_irq(s->irq, 1);
30daca13d4SGuenter Roeck }
31daca13d4SGuenter Roeck
imx2_wdt_expired(void * opaque)32daca13d4SGuenter Roeck static void imx2_wdt_expired(void *opaque)
33daca13d4SGuenter Roeck {
34daca13d4SGuenter Roeck IMX2WdtState *s = IMX2_WDT(opaque);
35daca13d4SGuenter Roeck
3688a9973eSBernhard Beschow trace_imx2_wdt_expired();
3788a9973eSBernhard Beschow
38daca13d4SGuenter Roeck s->wrsr = IMX2_WDT_WRSR_TOUT;
39daca13d4SGuenter Roeck
40daca13d4SGuenter Roeck /* Perform watchdog action if watchdog is enabled */
41daca13d4SGuenter Roeck if (s->wcr & IMX2_WDT_WCR_WDE) {
42daca13d4SGuenter Roeck watchdog_perform_action();
43daca13d4SGuenter Roeck }
44daca13d4SGuenter Roeck }
45daca13d4SGuenter Roeck
imx2_wdt_reset(DeviceState * dev)46daca13d4SGuenter Roeck static void imx2_wdt_reset(DeviceState *dev)
47daca13d4SGuenter Roeck {
48daca13d4SGuenter Roeck IMX2WdtState *s = IMX2_WDT(dev);
49daca13d4SGuenter Roeck
50daca13d4SGuenter Roeck ptimer_transaction_begin(s->timer);
51daca13d4SGuenter Roeck ptimer_stop(s->timer);
52daca13d4SGuenter Roeck ptimer_transaction_commit(s->timer);
53daca13d4SGuenter Roeck
54daca13d4SGuenter Roeck if (s->pretimeout_support) {
55daca13d4SGuenter Roeck ptimer_transaction_begin(s->itimer);
56daca13d4SGuenter Roeck ptimer_stop(s->itimer);
57daca13d4SGuenter Roeck ptimer_transaction_commit(s->itimer);
58daca13d4SGuenter Roeck }
59daca13d4SGuenter Roeck
60daca13d4SGuenter Roeck s->wicr_locked = false;
61daca13d4SGuenter Roeck s->wcr_locked = false;
62daca13d4SGuenter Roeck s->wcr_wde_locked = false;
63daca13d4SGuenter Roeck
64daca13d4SGuenter Roeck s->wcr = IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS;
65daca13d4SGuenter Roeck s->wsr = 0;
66daca13d4SGuenter Roeck s->wrsr &= ~(IMX2_WDT_WRSR_TOUT | IMX2_WDT_WRSR_SFTW);
67daca13d4SGuenter Roeck s->wicr = IMX2_WDT_WICR_WICT_DEF;
68daca13d4SGuenter Roeck s->wmcr = IMX2_WDT_WMCR_PDE;
69daca13d4SGuenter Roeck }
70daca13d4SGuenter Roeck
imx2_wdt_read(void * opaque,hwaddr addr,unsigned int size)71daca13d4SGuenter Roeck static uint64_t imx2_wdt_read(void *opaque, hwaddr addr, unsigned int size)
72daca13d4SGuenter Roeck {
73daca13d4SGuenter Roeck IMX2WdtState *s = IMX2_WDT(opaque);
7418736a21SBernhard Beschow uint16_t value = 0;
75daca13d4SGuenter Roeck
76daca13d4SGuenter Roeck switch (addr) {
77daca13d4SGuenter Roeck case IMX2_WDT_WCR:
7818736a21SBernhard Beschow value = s->wcr;
7918736a21SBernhard Beschow break;
80daca13d4SGuenter Roeck case IMX2_WDT_WSR:
8118736a21SBernhard Beschow value = s->wsr;
8218736a21SBernhard Beschow break;
83daca13d4SGuenter Roeck case IMX2_WDT_WRSR:
8418736a21SBernhard Beschow value = s->wrsr;
8518736a21SBernhard Beschow break;
86daca13d4SGuenter Roeck case IMX2_WDT_WICR:
8718736a21SBernhard Beschow value = s->wicr;
8818736a21SBernhard Beschow break;
89daca13d4SGuenter Roeck case IMX2_WDT_WMCR:
9018736a21SBernhard Beschow value = s->wmcr;
9118736a21SBernhard Beschow break;
92daca13d4SGuenter Roeck }
9318736a21SBernhard Beschow
9418736a21SBernhard Beschow trace_imx2_wdt_read(addr, value);
9518736a21SBernhard Beschow
9618736a21SBernhard Beschow return value;
9737f95959SGuenter Roeck }
9837f95959SGuenter Roeck
imx_wdt2_update_itimer(IMX2WdtState * s,bool start)99daca13d4SGuenter Roeck static void imx_wdt2_update_itimer(IMX2WdtState *s, bool start)
100daca13d4SGuenter Roeck {
101daca13d4SGuenter Roeck bool running = (s->wcr & IMX2_WDT_WCR_WDE) && (s->wcr & IMX2_WDT_WCR_WT);
102daca13d4SGuenter Roeck bool enabled = s->wicr & IMX2_WDT_WICR_WIE;
103daca13d4SGuenter Roeck
104daca13d4SGuenter Roeck ptimer_transaction_begin(s->itimer);
105daca13d4SGuenter Roeck if (start || !enabled) {
106daca13d4SGuenter Roeck ptimer_stop(s->itimer);
107daca13d4SGuenter Roeck }
108daca13d4SGuenter Roeck if (running && enabled) {
109daca13d4SGuenter Roeck int count = ptimer_get_count(s->timer);
110daca13d4SGuenter Roeck int pretimeout = s->wicr & IMX2_WDT_WICR_WICT;
111daca13d4SGuenter Roeck
112daca13d4SGuenter Roeck /*
113daca13d4SGuenter Roeck * Only (re-)start pretimeout timer if its counter value is larger
114daca13d4SGuenter Roeck * than 0. Otherwise it will fire right away and we'll get an
115daca13d4SGuenter Roeck * interrupt loop.
116daca13d4SGuenter Roeck */
117daca13d4SGuenter Roeck if (count > pretimeout) {
118daca13d4SGuenter Roeck ptimer_set_count(s->itimer, count - pretimeout);
119daca13d4SGuenter Roeck if (start) {
120daca13d4SGuenter Roeck ptimer_run(s->itimer, 1);
121daca13d4SGuenter Roeck }
122daca13d4SGuenter Roeck }
123daca13d4SGuenter Roeck }
124daca13d4SGuenter Roeck ptimer_transaction_commit(s->itimer);
125daca13d4SGuenter Roeck }
126daca13d4SGuenter Roeck
imx_wdt2_update_timer(IMX2WdtState * s,bool start)127daca13d4SGuenter Roeck static void imx_wdt2_update_timer(IMX2WdtState *s, bool start)
128daca13d4SGuenter Roeck {
129daca13d4SGuenter Roeck ptimer_transaction_begin(s->timer);
130daca13d4SGuenter Roeck if (start) {
131daca13d4SGuenter Roeck ptimer_stop(s->timer);
132daca13d4SGuenter Roeck }
133daca13d4SGuenter Roeck if ((s->wcr & IMX2_WDT_WCR_WDE) && (s->wcr & IMX2_WDT_WCR_WT)) {
134daca13d4SGuenter Roeck int count = (s->wcr & IMX2_WDT_WCR_WT) >> 8;
135daca13d4SGuenter Roeck
136daca13d4SGuenter Roeck /* A value of 0 reflects one period (0.5s). */
137daca13d4SGuenter Roeck ptimer_set_count(s->timer, count + 1);
138daca13d4SGuenter Roeck if (start) {
139daca13d4SGuenter Roeck ptimer_run(s->timer, 1);
140daca13d4SGuenter Roeck }
141daca13d4SGuenter Roeck }
142daca13d4SGuenter Roeck ptimer_transaction_commit(s->timer);
143daca13d4SGuenter Roeck if (s->pretimeout_support) {
144daca13d4SGuenter Roeck imx_wdt2_update_itimer(s, start);
145daca13d4SGuenter Roeck }
146daca13d4SGuenter Roeck }
147daca13d4SGuenter Roeck
imx2_wdt_write(void * opaque,hwaddr addr,uint64_t value,unsigned int size)14837f95959SGuenter Roeck static void imx2_wdt_write(void *opaque, hwaddr addr,
14937f95959SGuenter Roeck uint64_t value, unsigned int size)
15037f95959SGuenter Roeck {
151daca13d4SGuenter Roeck IMX2WdtState *s = IMX2_WDT(opaque);
152daca13d4SGuenter Roeck
15318736a21SBernhard Beschow trace_imx2_wdt_write(addr, value);
15418736a21SBernhard Beschow
155daca13d4SGuenter Roeck switch (addr) {
156daca13d4SGuenter Roeck case IMX2_WDT_WCR:
157daca13d4SGuenter Roeck if (s->wcr_locked) {
158daca13d4SGuenter Roeck value &= ~IMX2_WDT_WCR_LOCK_MASK;
159daca13d4SGuenter Roeck value |= (s->wicr & IMX2_WDT_WCR_LOCK_MASK);
160daca13d4SGuenter Roeck }
161daca13d4SGuenter Roeck s->wcr_locked = true;
162daca13d4SGuenter Roeck if (s->wcr_wde_locked) {
163daca13d4SGuenter Roeck value &= ~IMX2_WDT_WCR_WDE;
164daca13d4SGuenter Roeck value |= (s->wicr & ~IMX2_WDT_WCR_WDE);
165daca13d4SGuenter Roeck } else if (value & IMX2_WDT_WCR_WDE) {
166daca13d4SGuenter Roeck s->wcr_wde_locked = true;
167daca13d4SGuenter Roeck }
168daca13d4SGuenter Roeck if (s->wcr_wdt_locked) {
169daca13d4SGuenter Roeck value &= ~IMX2_WDT_WCR_WDT;
170daca13d4SGuenter Roeck value |= (s->wicr & ~IMX2_WDT_WCR_WDT);
171daca13d4SGuenter Roeck } else if (value & IMX2_WDT_WCR_WDT) {
172daca13d4SGuenter Roeck s->wcr_wdt_locked = true;
173daca13d4SGuenter Roeck }
174daca13d4SGuenter Roeck
175daca13d4SGuenter Roeck s->wcr = value;
176daca13d4SGuenter Roeck if (!(value & IMX2_WDT_WCR_SRS)) {
177daca13d4SGuenter Roeck s->wrsr = IMX2_WDT_WRSR_SFTW;
178daca13d4SGuenter Roeck }
179daca13d4SGuenter Roeck if (!(value & (IMX2_WDT_WCR_WDA | IMX2_WDT_WCR_SRS)) ||
180daca13d4SGuenter Roeck (!(value & IMX2_WDT_WCR_WT) && (value & IMX2_WDT_WCR_WDE))) {
18137f95959SGuenter Roeck watchdog_perform_action();
18237f95959SGuenter Roeck }
183daca13d4SGuenter Roeck s->wcr |= IMX2_WDT_WCR_SRS;
184daca13d4SGuenter Roeck imx_wdt2_update_timer(s, true);
185daca13d4SGuenter Roeck break;
186daca13d4SGuenter Roeck case IMX2_WDT_WSR:
187daca13d4SGuenter Roeck if (s->wsr == IMX2_WDT_SEQ1 && value == IMX2_WDT_SEQ2) {
188daca13d4SGuenter Roeck imx_wdt2_update_timer(s, false);
189daca13d4SGuenter Roeck }
190daca13d4SGuenter Roeck s->wsr = value;
191daca13d4SGuenter Roeck break;
192daca13d4SGuenter Roeck case IMX2_WDT_WRSR:
193daca13d4SGuenter Roeck break;
194daca13d4SGuenter Roeck case IMX2_WDT_WICR:
195daca13d4SGuenter Roeck if (!s->pretimeout_support) {
196daca13d4SGuenter Roeck return;
197daca13d4SGuenter Roeck }
198daca13d4SGuenter Roeck value &= IMX2_WDT_WICR_LOCK_MASK | IMX2_WDT_WICR_WTIS;
199daca13d4SGuenter Roeck if (s->wicr_locked) {
200daca13d4SGuenter Roeck value &= IMX2_WDT_WICR_WTIS;
201daca13d4SGuenter Roeck value |= (s->wicr & IMX2_WDT_WICR_LOCK_MASK);
202daca13d4SGuenter Roeck }
203daca13d4SGuenter Roeck s->wicr = value | (s->wicr & IMX2_WDT_WICR_WTIS);
204daca13d4SGuenter Roeck if (value & IMX2_WDT_WICR_WTIS) {
205daca13d4SGuenter Roeck s->wicr &= ~IMX2_WDT_WICR_WTIS;
206daca13d4SGuenter Roeck qemu_set_irq(s->irq, 0);
207daca13d4SGuenter Roeck }
208daca13d4SGuenter Roeck imx_wdt2_update_itimer(s, true);
209daca13d4SGuenter Roeck s->wicr_locked = true;
210daca13d4SGuenter Roeck break;
211daca13d4SGuenter Roeck case IMX2_WDT_WMCR:
212daca13d4SGuenter Roeck s->wmcr = value & IMX2_WDT_WMCR_PDE;
213daca13d4SGuenter Roeck break;
214daca13d4SGuenter Roeck }
21537f95959SGuenter Roeck }
21637f95959SGuenter Roeck
21737f95959SGuenter Roeck static const MemoryRegionOps imx2_wdt_ops = {
21837f95959SGuenter Roeck .read = imx2_wdt_read,
21937f95959SGuenter Roeck .write = imx2_wdt_write,
22037f95959SGuenter Roeck .endianness = DEVICE_NATIVE_ENDIAN,
22137f95959SGuenter Roeck .impl = {
22237f95959SGuenter Roeck /*
22337f95959SGuenter Roeck * Our device would not work correctly if the guest was doing
22437f95959SGuenter Roeck * unaligned access. This might not be a limitation on the
22537f95959SGuenter Roeck * real device but in practice there is no reason for a guest
22637f95959SGuenter Roeck * to access this device unaligned.
22737f95959SGuenter Roeck */
228daca13d4SGuenter Roeck .min_access_size = 2,
229daca13d4SGuenter Roeck .max_access_size = 2,
23037f95959SGuenter Roeck .unaligned = false,
23137f95959SGuenter Roeck },
23237f95959SGuenter Roeck };
23337f95959SGuenter Roeck
234daca13d4SGuenter Roeck static const VMStateDescription vmstate_imx2_wdt = {
235daca13d4SGuenter Roeck .name = "imx2.wdt",
23645bc669eSRichard Henderson .fields = (const VMStateField[]) {
237daca13d4SGuenter Roeck VMSTATE_PTIMER(timer, IMX2WdtState),
238daca13d4SGuenter Roeck VMSTATE_PTIMER(itimer, IMX2WdtState),
239daca13d4SGuenter Roeck VMSTATE_BOOL(wicr_locked, IMX2WdtState),
240daca13d4SGuenter Roeck VMSTATE_BOOL(wcr_locked, IMX2WdtState),
241daca13d4SGuenter Roeck VMSTATE_BOOL(wcr_wde_locked, IMX2WdtState),
242daca13d4SGuenter Roeck VMSTATE_BOOL(wcr_wdt_locked, IMX2WdtState),
243daca13d4SGuenter Roeck VMSTATE_UINT16(wcr, IMX2WdtState),
244daca13d4SGuenter Roeck VMSTATE_UINT16(wsr, IMX2WdtState),
245daca13d4SGuenter Roeck VMSTATE_UINT16(wrsr, IMX2WdtState),
246daca13d4SGuenter Roeck VMSTATE_UINT16(wmcr, IMX2WdtState),
247daca13d4SGuenter Roeck VMSTATE_UINT16(wicr, IMX2WdtState),
248daca13d4SGuenter Roeck VMSTATE_END_OF_LIST()
249daca13d4SGuenter Roeck }
250daca13d4SGuenter Roeck };
251daca13d4SGuenter Roeck
imx2_wdt_realize(DeviceState * dev,Error ** errp)25237f95959SGuenter Roeck static void imx2_wdt_realize(DeviceState *dev, Error **errp)
25337f95959SGuenter Roeck {
25437f95959SGuenter Roeck IMX2WdtState *s = IMX2_WDT(dev);
255daca13d4SGuenter Roeck SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
25637f95959SGuenter Roeck
25737f95959SGuenter Roeck memory_region_init_io(&s->mmio, OBJECT(dev),
25837f95959SGuenter Roeck &imx2_wdt_ops, s,
259daca13d4SGuenter Roeck TYPE_IMX2_WDT,
260daca13d4SGuenter Roeck IMX2_WDT_MMIO_SIZE);
261daca13d4SGuenter Roeck sysbus_init_mmio(sbd, &s->mmio);
262daca13d4SGuenter Roeck sysbus_init_irq(sbd, &s->irq);
263daca13d4SGuenter Roeck
264daca13d4SGuenter Roeck s->timer = ptimer_init(imx2_wdt_expired, s,
265daca13d4SGuenter Roeck PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
266daca13d4SGuenter Roeck PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
267daca13d4SGuenter Roeck PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
268daca13d4SGuenter Roeck ptimer_transaction_begin(s->timer);
269daca13d4SGuenter Roeck ptimer_set_freq(s->timer, 2);
270daca13d4SGuenter Roeck ptimer_set_limit(s->timer, 0xff, 1);
271daca13d4SGuenter Roeck ptimer_transaction_commit(s->timer);
272daca13d4SGuenter Roeck if (s->pretimeout_support) {
273daca13d4SGuenter Roeck s->itimer = ptimer_init(imx2_wdt_interrupt, s,
274daca13d4SGuenter Roeck PTIMER_POLICY_NO_IMMEDIATE_TRIGGER |
275daca13d4SGuenter Roeck PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
276daca13d4SGuenter Roeck PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
277daca13d4SGuenter Roeck ptimer_transaction_begin(s->itimer);
278daca13d4SGuenter Roeck ptimer_set_freq(s->itimer, 2);
279daca13d4SGuenter Roeck ptimer_set_limit(s->itimer, 0xff, 1);
280daca13d4SGuenter Roeck ptimer_transaction_commit(s->itimer);
28137f95959SGuenter Roeck }
282daca13d4SGuenter Roeck }
283daca13d4SGuenter Roeck
284daca13d4SGuenter Roeck static Property imx2_wdt_properties[] = {
285daca13d4SGuenter Roeck DEFINE_PROP_BOOL("pretimeout-support", IMX2WdtState, pretimeout_support,
286daca13d4SGuenter Roeck false),
287daca13d4SGuenter Roeck DEFINE_PROP_END_OF_LIST()
288daca13d4SGuenter Roeck };
28937f95959SGuenter Roeck
imx2_wdt_class_init(ObjectClass * klass,void * data)29037f95959SGuenter Roeck static void imx2_wdt_class_init(ObjectClass *klass, void *data)
29137f95959SGuenter Roeck {
29237f95959SGuenter Roeck DeviceClass *dc = DEVICE_CLASS(klass);
29337f95959SGuenter Roeck
294daca13d4SGuenter Roeck device_class_set_props(dc, imx2_wdt_properties);
29537f95959SGuenter Roeck dc->realize = imx2_wdt_realize;
296*e3d08143SPeter Maydell device_class_set_legacy_reset(dc, imx2_wdt_reset);
297daca13d4SGuenter Roeck dc->vmsd = &vmstate_imx2_wdt;
298b10cb627SPaolo Bonzini dc->desc = "i.MX2 watchdog timer";
299b10cb627SPaolo Bonzini set_bit(DEVICE_CATEGORY_WATCHDOG, dc->categories);
30037f95959SGuenter Roeck }
30137f95959SGuenter Roeck
30237f95959SGuenter Roeck static const TypeInfo imx2_wdt_info = {
30337f95959SGuenter Roeck .name = TYPE_IMX2_WDT,
30437f95959SGuenter Roeck .parent = TYPE_SYS_BUS_DEVICE,
30537f95959SGuenter Roeck .instance_size = sizeof(IMX2WdtState),
30637f95959SGuenter Roeck .class_init = imx2_wdt_class_init,
30737f95959SGuenter Roeck };
30837f95959SGuenter Roeck
imx2_wdt_register_type(void)30937f95959SGuenter Roeck static void imx2_wdt_register_type(void)
31037f95959SGuenter Roeck {
31137f95959SGuenter Roeck type_register_static(&imx2_wdt_info);
31237f95959SGuenter Roeck }
31337f95959SGuenter Roeck type_init(imx2_wdt_register_type)
314