]>
Commit | Line | Data |
---|---|---|
067e68e7 AS |
1 | /* |
2 | * Copyright (c) 2018, Impinj, Inc. | |
3 | * | |
4 | * i.MX2 Watchdog IP block | |
5 | * | |
6 | * Author: Andrey Smirnov <[email protected]> | |
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" | |
0b8fa32f | 14 | #include "qemu/module.h" |
067e68e7 | 15 | #include "sysemu/watchdog.h" |
daca13d4 GR |
16 | #include "migration/vmstate.h" |
17 | #include "hw/qdev-properties.h" | |
067e68e7 | 18 | |
37f95959 | 19 | #include "hw/watchdog/wdt_imx2.h" |
067e68e7 | 20 | |
daca13d4 GR |
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 | } | |
067e68e7 | 66 | |
daca13d4 | 67 | static uint64_t imx2_wdt_read(void *opaque, hwaddr addr, unsigned int size) |
067e68e7 | 68 | { |
daca13d4 GR |
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 | } | |
067e68e7 AS |
83 | return 0; |
84 | } | |
85 | ||
daca13d4 GR |
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 | ||
067e68e7 AS |
135 | static void imx2_wdt_write(void *opaque, hwaddr addr, |
136 | uint64_t value, unsigned int size) | |
137 | { | |
daca13d4 GR |
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; | |
067e68e7 AS |
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 | */ | |
daca13d4 GR |
213 | .min_access_size = 2, |
214 | .max_access_size = 2, | |
067e68e7 AS |
215 | .unaligned = false, |
216 | }, | |
217 | }; | |
218 | ||
daca13d4 GR |
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 | ||
067e68e7 AS |
237 | static void imx2_wdt_realize(DeviceState *dev, Error **errp) |
238 | { | |
239 | IMX2WdtState *s = IMX2_WDT(dev); | |
daca13d4 | 240 | SysBusDevice *sbd = SYS_BUS_DEVICE(dev); |
067e68e7 AS |
241 | |
242 | memory_region_init_io(&s->mmio, OBJECT(dev), | |
243 | &imx2_wdt_ops, s, | |
daca13d4 GR |
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 | } | |
067e68e7 AS |
267 | } |
268 | ||
daca13d4 GR |
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 | ||
067e68e7 AS |
275 | static void imx2_wdt_class_init(ObjectClass *klass, void *data) |
276 | { | |
277 | DeviceClass *dc = DEVICE_CLASS(klass); | |
278 | ||
daca13d4 | 279 | device_class_set_props(dc, imx2_wdt_properties); |
067e68e7 | 280 | dc->realize = imx2_wdt_realize; |
daca13d4 GR |
281 | dc->reset = imx2_wdt_reset; |
282 | dc->vmsd = &vmstate_imx2_wdt; | |
283 | dc->desc = "i.MX watchdog timer"; | |
067e68e7 AS |
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) |