]>
Commit | Line | Data |
---|---|---|
85fdd74f HS |
1 | /* |
2 | * Nuvoton NPCM7xx Timer Controller | |
3 | * | |
4 | * Copyright 2020 Google LLC | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License as published by the | |
8 | * Free Software Foundation; either version 2 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * This program is distributed in the hope that it will be useful, but WITHOUT | |
12 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
13 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
14 | * for more details. | |
15 | */ | |
16 | ||
17 | #include "qemu/osdep.h" | |
18 | ||
19 | #include "hw/irq.h" | |
0be12dc7 | 20 | #include "hw/qdev-clock.h" |
7d378ed6 | 21 | #include "hw/qdev-properties.h" |
85fdd74f HS |
22 | #include "hw/timer/npcm7xx_timer.h" |
23 | #include "migration/vmstate.h" | |
24 | #include "qemu/bitops.h" | |
25 | #include "qemu/error-report.h" | |
26 | #include "qemu/log.h" | |
27 | #include "qemu/module.h" | |
28 | #include "qemu/timer.h" | |
29 | #include "qemu/units.h" | |
30 | #include "trace.h" | |
31 | ||
32 | /* 32-bit register indices. */ | |
33 | enum NPCM7xxTimerRegisters { | |
34 | NPCM7XX_TIMER_TCSR0, | |
35 | NPCM7XX_TIMER_TCSR1, | |
36 | NPCM7XX_TIMER_TICR0, | |
37 | NPCM7XX_TIMER_TICR1, | |
38 | NPCM7XX_TIMER_TDR0, | |
39 | NPCM7XX_TIMER_TDR1, | |
40 | NPCM7XX_TIMER_TISR, | |
41 | NPCM7XX_TIMER_WTCR, | |
42 | NPCM7XX_TIMER_TCSR2, | |
43 | NPCM7XX_TIMER_TCSR3, | |
44 | NPCM7XX_TIMER_TICR2, | |
45 | NPCM7XX_TIMER_TICR3, | |
46 | NPCM7XX_TIMER_TDR2, | |
47 | NPCM7XX_TIMER_TDR3, | |
48 | NPCM7XX_TIMER_TCSR4 = 0x0040 / sizeof(uint32_t), | |
49 | NPCM7XX_TIMER_TICR4 = 0x0048 / sizeof(uint32_t), | |
50 | NPCM7XX_TIMER_TDR4 = 0x0050 / sizeof(uint32_t), | |
51 | NPCM7XX_TIMER_REGS_END, | |
52 | }; | |
53 | ||
54 | /* Register field definitions. */ | |
55 | #define NPCM7XX_TCSR_CEN BIT(30) | |
56 | #define NPCM7XX_TCSR_IE BIT(29) | |
57 | #define NPCM7XX_TCSR_PERIODIC BIT(27) | |
58 | #define NPCM7XX_TCSR_CRST BIT(26) | |
59 | #define NPCM7XX_TCSR_CACT BIT(25) | |
60 | #define NPCM7XX_TCSR_RSVD 0x01ffff00 | |
61 | #define NPCM7XX_TCSR_PRESCALE_START 0 | |
62 | #define NPCM7XX_TCSR_PRESCALE_LEN 8 | |
63 | ||
7d378ed6 HW |
64 | #define NPCM7XX_WTCR_WTCLK(rv) extract32(rv, 10, 2) |
65 | #define NPCM7XX_WTCR_FREEZE_EN BIT(9) | |
66 | #define NPCM7XX_WTCR_WTE BIT(7) | |
67 | #define NPCM7XX_WTCR_WTIE BIT(6) | |
68 | #define NPCM7XX_WTCR_WTIS(rv) extract32(rv, 4, 2) | |
69 | #define NPCM7XX_WTCR_WTIF BIT(3) | |
70 | #define NPCM7XX_WTCR_WTRF BIT(2) | |
71 | #define NPCM7XX_WTCR_WTRE BIT(1) | |
72 | #define NPCM7XX_WTCR_WTR BIT(0) | |
73 | ||
74 | /* | |
75 | * The number of clock cycles between interrupt and reset in watchdog, used | |
76 | * by the software to handle the interrupt before system is reset. | |
77 | */ | |
78 | #define NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES 1024 | |
79 | ||
80 | /* Start or resume the timer. */ | |
81 | static void npcm7xx_timer_start(NPCM7xxBaseTimer *t) | |
82 | { | |
83 | int64_t now; | |
84 | ||
85 | now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
86 | t->expires_ns = now + t->remaining_ns; | |
87 | timer_mod(&t->qtimer, t->expires_ns); | |
88 | } | |
89 | ||
90 | /* Stop counting. Record the time remaining so we can continue later. */ | |
91 | static void npcm7xx_timer_pause(NPCM7xxBaseTimer *t) | |
92 | { | |
93 | int64_t now; | |
94 | ||
95 | timer_del(&t->qtimer); | |
96 | now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
97 | t->remaining_ns = t->expires_ns - now; | |
98 | } | |
99 | ||
100 | /* Delete the timer and reset it to default state. */ | |
101 | static void npcm7xx_timer_clear(NPCM7xxBaseTimer *t) | |
102 | { | |
103 | timer_del(&t->qtimer); | |
104 | t->expires_ns = 0; | |
105 | t->remaining_ns = 0; | |
106 | } | |
107 | ||
85fdd74f HS |
108 | /* |
109 | * Returns the index of timer in the tc->timer array. This can be used to | |
110 | * locate the registers that belong to this timer. | |
111 | */ | |
112 | static int npcm7xx_timer_index(NPCM7xxTimerCtrlState *tc, NPCM7xxTimer *timer) | |
113 | { | |
114 | int index = timer - tc->timer; | |
115 | ||
116 | g_assert(index >= 0 && index < NPCM7XX_TIMERS_PER_CTRL); | |
117 | ||
118 | return index; | |
119 | } | |
120 | ||
121 | /* Return the value by which to divide the reference clock rate. */ | |
122 | static uint32_t npcm7xx_tcsr_prescaler(uint32_t tcsr) | |
123 | { | |
124 | return extract32(tcsr, NPCM7XX_TCSR_PRESCALE_START, | |
125 | NPCM7XX_TCSR_PRESCALE_LEN) + 1; | |
126 | } | |
127 | ||
128 | /* Convert a timer cycle count to a time interval in nanoseconds. */ | |
129 | static int64_t npcm7xx_timer_count_to_ns(NPCM7xxTimer *t, uint32_t count) | |
130 | { | |
0be12dc7 | 131 | int64_t ticks = count; |
85fdd74f | 132 | |
0be12dc7 | 133 | ticks *= npcm7xx_tcsr_prescaler(t->tcsr); |
85fdd74f | 134 | |
0be12dc7 | 135 | return clock_ticks_to_ns(t->ctrl->clock, ticks); |
85fdd74f HS |
136 | } |
137 | ||
138 | /* Convert a time interval in nanoseconds to a timer cycle count. */ | |
139 | static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns) | |
140 | { | |
c7db11b0 PM |
141 | return clock_ns_to_ticks(t->ctrl->clock, ns) / |
142 | npcm7xx_tcsr_prescaler(t->tcsr); | |
85fdd74f HS |
143 | } |
144 | ||
7d378ed6 HW |
145 | static uint32_t npcm7xx_watchdog_timer_prescaler(const NPCM7xxWatchdogTimer *t) |
146 | { | |
147 | switch (NPCM7XX_WTCR_WTCLK(t->wtcr)) { | |
148 | case 0: | |
149 | return 1; | |
150 | case 1: | |
151 | return 256; | |
152 | case 2: | |
153 | return 2048; | |
154 | case 3: | |
155 | return 65536; | |
156 | default: | |
157 | g_assert_not_reached(); | |
158 | } | |
159 | } | |
160 | ||
161 | static void npcm7xx_watchdog_timer_reset_cycles(NPCM7xxWatchdogTimer *t, | |
162 | int64_t cycles) | |
163 | { | |
0be12dc7 HW |
164 | int64_t ticks = cycles * npcm7xx_watchdog_timer_prescaler(t); |
165 | int64_t ns = clock_ticks_to_ns(t->ctrl->clock, ticks); | |
7d378ed6 HW |
166 | |
167 | /* | |
168 | * The reset function always clears the current timer. The caller of the | |
169 | * this needs to decide whether to start the watchdog timer based on | |
170 | * specific flag in WTCR. | |
171 | */ | |
172 | npcm7xx_timer_clear(&t->base_timer); | |
173 | ||
7d378ed6 HW |
174 | t->base_timer.remaining_ns = ns; |
175 | } | |
176 | ||
177 | static void npcm7xx_watchdog_timer_reset(NPCM7xxWatchdogTimer *t) | |
178 | { | |
179 | int64_t cycles = 1; | |
180 | uint32_t s = NPCM7XX_WTCR_WTIS(t->wtcr); | |
181 | ||
182 | g_assert(s <= 3); | |
183 | ||
184 | cycles <<= NPCM7XX_WATCHDOG_BASETIME_SHIFT; | |
185 | cycles <<= 2 * s; | |
186 | ||
187 | npcm7xx_watchdog_timer_reset_cycles(t, cycles); | |
188 | } | |
189 | ||
85fdd74f HS |
190 | /* |
191 | * Raise the interrupt line if there's a pending interrupt and interrupts are | |
192 | * enabled for this timer. If not, lower it. | |
193 | */ | |
194 | static void npcm7xx_timer_check_interrupt(NPCM7xxTimer *t) | |
195 | { | |
196 | NPCM7xxTimerCtrlState *tc = t->ctrl; | |
197 | int index = npcm7xx_timer_index(tc, t); | |
198 | bool pending = (t->tcsr & NPCM7XX_TCSR_IE) && (tc->tisr & BIT(index)); | |
199 | ||
200 | qemu_set_irq(t->irq, pending); | |
201 | trace_npcm7xx_timer_irq(DEVICE(tc)->canonical_path, index, pending); | |
202 | } | |
203 | ||
85fdd74f HS |
204 | /* |
205 | * Called when the counter reaches zero. Sets the interrupt flag, and either | |
206 | * restarts or disables the timer. | |
207 | */ | |
208 | static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t) | |
209 | { | |
210 | NPCM7xxTimerCtrlState *tc = t->ctrl; | |
211 | int index = npcm7xx_timer_index(tc, t); | |
212 | ||
213 | tc->tisr |= BIT(index); | |
214 | ||
215 | if (t->tcsr & NPCM7XX_TCSR_PERIODIC) { | |
7d378ed6 | 216 | t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr); |
85fdd74f | 217 | if (t->tcsr & NPCM7XX_TCSR_CEN) { |
7d378ed6 | 218 | npcm7xx_timer_start(&t->base_timer); |
85fdd74f HS |
219 | } |
220 | } else { | |
221 | t->tcsr &= ~(NPCM7XX_TCSR_CEN | NPCM7XX_TCSR_CACT); | |
222 | } | |
223 | ||
224 | npcm7xx_timer_check_interrupt(t); | |
225 | } | |
226 | ||
85fdd74f HS |
227 | |
228 | /* | |
229 | * Restart the timer from its initial value. If the timer was enabled and stays | |
230 | * enabled, adjust the QEMU timer according to the new count. If the timer is | |
231 | * transitioning from disabled to enabled, the caller is expected to start the | |
232 | * timer later. | |
233 | */ | |
234 | static void npcm7xx_timer_restart(NPCM7xxTimer *t, uint32_t old_tcsr) | |
235 | { | |
7d378ed6 | 236 | t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr); |
85fdd74f HS |
237 | |
238 | if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) { | |
7d378ed6 | 239 | npcm7xx_timer_start(&t->base_timer); |
85fdd74f HS |
240 | } |
241 | } | |
242 | ||
243 | /* Register read and write handlers */ | |
244 | ||
245 | static uint32_t npcm7xx_timer_read_tdr(NPCM7xxTimer *t) | |
246 | { | |
247 | if (t->tcsr & NPCM7XX_TCSR_CEN) { | |
248 | int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); | |
249 | ||
7d378ed6 | 250 | return npcm7xx_timer_ns_to_count(t, t->base_timer.expires_ns - now); |
85fdd74f HS |
251 | } |
252 | ||
7d378ed6 | 253 | return npcm7xx_timer_ns_to_count(t, t->base_timer.remaining_ns); |
85fdd74f HS |
254 | } |
255 | ||
256 | static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr) | |
257 | { | |
258 | uint32_t old_tcsr = t->tcsr; | |
259 | uint32_t tdr; | |
260 | ||
261 | if (new_tcsr & NPCM7XX_TCSR_RSVD) { | |
262 | qemu_log_mask(LOG_GUEST_ERROR, "%s: reserved bits in 0x%08x ignored\n", | |
263 | __func__, new_tcsr); | |
264 | new_tcsr &= ~NPCM7XX_TCSR_RSVD; | |
265 | } | |
266 | if (new_tcsr & NPCM7XX_TCSR_CACT) { | |
267 | qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only bits in 0x%08x ignored\n", | |
268 | __func__, new_tcsr); | |
269 | new_tcsr &= ~NPCM7XX_TCSR_CACT; | |
270 | } | |
271 | if ((new_tcsr & NPCM7XX_TCSR_CRST) && (new_tcsr & NPCM7XX_TCSR_CEN)) { | |
272 | qemu_log_mask(LOG_GUEST_ERROR, | |
273 | "%s: both CRST and CEN set; ignoring CEN.\n", | |
274 | __func__); | |
275 | new_tcsr &= ~NPCM7XX_TCSR_CEN; | |
276 | } | |
277 | ||
278 | /* Calculate the value of TDR before potentially changing the prescaler. */ | |
279 | tdr = npcm7xx_timer_read_tdr(t); | |
280 | ||
281 | t->tcsr = (t->tcsr & NPCM7XX_TCSR_CACT) | new_tcsr; | |
282 | ||
283 | if (npcm7xx_tcsr_prescaler(old_tcsr) != npcm7xx_tcsr_prescaler(new_tcsr)) { | |
284 | /* Recalculate time remaining based on the current TDR value. */ | |
7d378ed6 | 285 | t->base_timer.remaining_ns = npcm7xx_timer_count_to_ns(t, tdr); |
85fdd74f | 286 | if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) { |
7d378ed6 | 287 | npcm7xx_timer_start(&t->base_timer); |
85fdd74f HS |
288 | } |
289 | } | |
290 | ||
291 | if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_IE) { | |
292 | npcm7xx_timer_check_interrupt(t); | |
293 | } | |
294 | if (new_tcsr & NPCM7XX_TCSR_CRST) { | |
295 | npcm7xx_timer_restart(t, old_tcsr); | |
296 | t->tcsr &= ~NPCM7XX_TCSR_CRST; | |
297 | } | |
298 | if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_CEN) { | |
299 | if (new_tcsr & NPCM7XX_TCSR_CEN) { | |
300 | t->tcsr |= NPCM7XX_TCSR_CACT; | |
7d378ed6 | 301 | npcm7xx_timer_start(&t->base_timer); |
85fdd74f HS |
302 | } else { |
303 | t->tcsr &= ~NPCM7XX_TCSR_CACT; | |
7d378ed6 HW |
304 | npcm7xx_timer_pause(&t->base_timer); |
305 | if (t->base_timer.remaining_ns <= 0) { | |
2ac88848 HS |
306 | npcm7xx_timer_reached_zero(t); |
307 | } | |
85fdd74f HS |
308 | } |
309 | } | |
310 | } | |
311 | ||
312 | static void npcm7xx_timer_write_ticr(NPCM7xxTimer *t, uint32_t new_ticr) | |
313 | { | |
314 | t->ticr = new_ticr; | |
315 | ||
316 | npcm7xx_timer_restart(t, t->tcsr); | |
317 | } | |
318 | ||
319 | static void npcm7xx_timer_write_tisr(NPCM7xxTimerCtrlState *s, uint32_t value) | |
320 | { | |
321 | int i; | |
322 | ||
323 | s->tisr &= ~value; | |
324 | for (i = 0; i < ARRAY_SIZE(s->timer); i++) { | |
325 | if (value & (1U << i)) { | |
326 | npcm7xx_timer_check_interrupt(&s->timer[i]); | |
327 | } | |
7d378ed6 | 328 | |
85fdd74f HS |
329 | } |
330 | } | |
331 | ||
7d378ed6 HW |
332 | static void npcm7xx_timer_write_wtcr(NPCM7xxWatchdogTimer *t, uint32_t new_wtcr) |
333 | { | |
334 | uint32_t old_wtcr = t->wtcr; | |
335 | ||
336 | /* | |
337 | * WTIF and WTRF are cleared by writing 1. Writing 0 makes these bits | |
338 | * unchanged. | |
339 | */ | |
340 | if (new_wtcr & NPCM7XX_WTCR_WTIF) { | |
341 | new_wtcr &= ~NPCM7XX_WTCR_WTIF; | |
342 | } else if (old_wtcr & NPCM7XX_WTCR_WTIF) { | |
343 | new_wtcr |= NPCM7XX_WTCR_WTIF; | |
344 | } | |
345 | if (new_wtcr & NPCM7XX_WTCR_WTRF) { | |
346 | new_wtcr &= ~NPCM7XX_WTCR_WTRF; | |
347 | } else if (old_wtcr & NPCM7XX_WTCR_WTRF) { | |
348 | new_wtcr |= NPCM7XX_WTCR_WTRF; | |
349 | } | |
350 | ||
351 | t->wtcr = new_wtcr; | |
352 | ||
353 | if (new_wtcr & NPCM7XX_WTCR_WTR) { | |
354 | t->wtcr &= ~NPCM7XX_WTCR_WTR; | |
355 | npcm7xx_watchdog_timer_reset(t); | |
356 | if (new_wtcr & NPCM7XX_WTCR_WTE) { | |
357 | npcm7xx_timer_start(&t->base_timer); | |
358 | } | |
359 | } else if ((old_wtcr ^ new_wtcr) & NPCM7XX_WTCR_WTE) { | |
360 | if (new_wtcr & NPCM7XX_WTCR_WTE) { | |
361 | npcm7xx_timer_start(&t->base_timer); | |
362 | } else { | |
363 | npcm7xx_timer_pause(&t->base_timer); | |
364 | } | |
365 | } | |
366 | ||
367 | } | |
368 | ||
85fdd74f HS |
369 | static hwaddr npcm7xx_tcsr_index(hwaddr reg) |
370 | { | |
371 | switch (reg) { | |
372 | case NPCM7XX_TIMER_TCSR0: | |
373 | return 0; | |
374 | case NPCM7XX_TIMER_TCSR1: | |
375 | return 1; | |
376 | case NPCM7XX_TIMER_TCSR2: | |
377 | return 2; | |
378 | case NPCM7XX_TIMER_TCSR3: | |
379 | return 3; | |
380 | case NPCM7XX_TIMER_TCSR4: | |
381 | return 4; | |
382 | default: | |
383 | g_assert_not_reached(); | |
384 | } | |
385 | } | |
386 | ||
387 | static hwaddr npcm7xx_ticr_index(hwaddr reg) | |
388 | { | |
389 | switch (reg) { | |
390 | case NPCM7XX_TIMER_TICR0: | |
391 | return 0; | |
392 | case NPCM7XX_TIMER_TICR1: | |
393 | return 1; | |
394 | case NPCM7XX_TIMER_TICR2: | |
395 | return 2; | |
396 | case NPCM7XX_TIMER_TICR3: | |
397 | return 3; | |
398 | case NPCM7XX_TIMER_TICR4: | |
399 | return 4; | |
400 | default: | |
401 | g_assert_not_reached(); | |
402 | } | |
403 | } | |
404 | ||
405 | static hwaddr npcm7xx_tdr_index(hwaddr reg) | |
406 | { | |
407 | switch (reg) { | |
408 | case NPCM7XX_TIMER_TDR0: | |
409 | return 0; | |
410 | case NPCM7XX_TIMER_TDR1: | |
411 | return 1; | |
412 | case NPCM7XX_TIMER_TDR2: | |
413 | return 2; | |
414 | case NPCM7XX_TIMER_TDR3: | |
415 | return 3; | |
416 | case NPCM7XX_TIMER_TDR4: | |
417 | return 4; | |
418 | default: | |
419 | g_assert_not_reached(); | |
420 | } | |
421 | } | |
422 | ||
423 | static uint64_t npcm7xx_timer_read(void *opaque, hwaddr offset, unsigned size) | |
424 | { | |
425 | NPCM7xxTimerCtrlState *s = opaque; | |
426 | uint64_t value = 0; | |
427 | hwaddr reg; | |
428 | ||
429 | reg = offset / sizeof(uint32_t); | |
430 | switch (reg) { | |
431 | case NPCM7XX_TIMER_TCSR0: | |
432 | case NPCM7XX_TIMER_TCSR1: | |
433 | case NPCM7XX_TIMER_TCSR2: | |
434 | case NPCM7XX_TIMER_TCSR3: | |
435 | case NPCM7XX_TIMER_TCSR4: | |
436 | value = s->timer[npcm7xx_tcsr_index(reg)].tcsr; | |
437 | break; | |
438 | ||
439 | case NPCM7XX_TIMER_TICR0: | |
440 | case NPCM7XX_TIMER_TICR1: | |
441 | case NPCM7XX_TIMER_TICR2: | |
442 | case NPCM7XX_TIMER_TICR3: | |
443 | case NPCM7XX_TIMER_TICR4: | |
444 | value = s->timer[npcm7xx_ticr_index(reg)].ticr; | |
445 | break; | |
446 | ||
447 | case NPCM7XX_TIMER_TDR0: | |
448 | case NPCM7XX_TIMER_TDR1: | |
449 | case NPCM7XX_TIMER_TDR2: | |
450 | case NPCM7XX_TIMER_TDR3: | |
451 | case NPCM7XX_TIMER_TDR4: | |
452 | value = npcm7xx_timer_read_tdr(&s->timer[npcm7xx_tdr_index(reg)]); | |
453 | break; | |
454 | ||
455 | case NPCM7XX_TIMER_TISR: | |
456 | value = s->tisr; | |
457 | break; | |
458 | ||
459 | case NPCM7XX_TIMER_WTCR: | |
7d378ed6 | 460 | value = s->watchdog_timer.wtcr; |
85fdd74f HS |
461 | break; |
462 | ||
463 | default: | |
464 | qemu_log_mask(LOG_GUEST_ERROR, | |
465 | "%s: invalid offset 0x%04" HWADDR_PRIx "\n", | |
466 | __func__, offset); | |
467 | break; | |
468 | } | |
469 | ||
470 | trace_npcm7xx_timer_read(DEVICE(s)->canonical_path, offset, value); | |
471 | ||
472 | return value; | |
473 | } | |
474 | ||
475 | static void npcm7xx_timer_write(void *opaque, hwaddr offset, | |
476 | uint64_t v, unsigned size) | |
477 | { | |
478 | uint32_t reg = offset / sizeof(uint32_t); | |
479 | NPCM7xxTimerCtrlState *s = opaque; | |
480 | uint32_t value = v; | |
481 | ||
482 | trace_npcm7xx_timer_write(DEVICE(s)->canonical_path, offset, value); | |
483 | ||
484 | switch (reg) { | |
485 | case NPCM7XX_TIMER_TCSR0: | |
486 | case NPCM7XX_TIMER_TCSR1: | |
487 | case NPCM7XX_TIMER_TCSR2: | |
488 | case NPCM7XX_TIMER_TCSR3: | |
489 | case NPCM7XX_TIMER_TCSR4: | |
490 | npcm7xx_timer_write_tcsr(&s->timer[npcm7xx_tcsr_index(reg)], value); | |
491 | return; | |
492 | ||
493 | case NPCM7XX_TIMER_TICR0: | |
494 | case NPCM7XX_TIMER_TICR1: | |
495 | case NPCM7XX_TIMER_TICR2: | |
496 | case NPCM7XX_TIMER_TICR3: | |
497 | case NPCM7XX_TIMER_TICR4: | |
498 | npcm7xx_timer_write_ticr(&s->timer[npcm7xx_ticr_index(reg)], value); | |
499 | return; | |
500 | ||
501 | case NPCM7XX_TIMER_TDR0: | |
502 | case NPCM7XX_TIMER_TDR1: | |
503 | case NPCM7XX_TIMER_TDR2: | |
504 | case NPCM7XX_TIMER_TDR3: | |
505 | case NPCM7XX_TIMER_TDR4: | |
506 | qemu_log_mask(LOG_GUEST_ERROR, | |
507 | "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n", | |
508 | __func__, offset); | |
509 | return; | |
510 | ||
511 | case NPCM7XX_TIMER_TISR: | |
512 | npcm7xx_timer_write_tisr(s, value); | |
513 | return; | |
514 | ||
515 | case NPCM7XX_TIMER_WTCR: | |
7d378ed6 | 516 | npcm7xx_timer_write_wtcr(&s->watchdog_timer, value); |
85fdd74f HS |
517 | return; |
518 | } | |
519 | ||
520 | qemu_log_mask(LOG_GUEST_ERROR, | |
521 | "%s: invalid offset 0x%04" HWADDR_PRIx "\n", | |
522 | __func__, offset); | |
523 | } | |
524 | ||
525 | static const struct MemoryRegionOps npcm7xx_timer_ops = { | |
526 | .read = npcm7xx_timer_read, | |
527 | .write = npcm7xx_timer_write, | |
528 | .endianness = DEVICE_LITTLE_ENDIAN, | |
529 | .valid = { | |
530 | .min_access_size = 4, | |
531 | .max_access_size = 4, | |
532 | .unaligned = false, | |
533 | }, | |
534 | }; | |
535 | ||
536 | /* Called when the QEMU timer expires. */ | |
537 | static void npcm7xx_timer_expired(void *opaque) | |
538 | { | |
539 | NPCM7xxTimer *t = opaque; | |
540 | ||
541 | if (t->tcsr & NPCM7XX_TCSR_CEN) { | |
542 | npcm7xx_timer_reached_zero(t); | |
543 | } | |
544 | } | |
545 | ||
546 | static void npcm7xx_timer_enter_reset(Object *obj, ResetType type) | |
547 | { | |
548 | NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj); | |
549 | int i; | |
550 | ||
551 | for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) { | |
552 | NPCM7xxTimer *t = &s->timer[i]; | |
553 | ||
7d378ed6 | 554 | npcm7xx_timer_clear(&t->base_timer); |
85fdd74f HS |
555 | t->tcsr = 0x00000005; |
556 | t->ticr = 0x00000000; | |
557 | } | |
558 | ||
559 | s->tisr = 0x00000000; | |
7d378ed6 HW |
560 | /* |
561 | * Set WTCLK to 1(default) and reset all flags except WTRF. | |
562 | * WTRF is not reset during a core domain reset. | |
563 | */ | |
564 | s->watchdog_timer.wtcr = 0x00000400 | (s->watchdog_timer.wtcr & | |
565 | NPCM7XX_WTCR_WTRF); | |
566 | } | |
567 | ||
568 | static void npcm7xx_watchdog_timer_expired(void *opaque) | |
569 | { | |
570 | NPCM7xxWatchdogTimer *t = opaque; | |
571 | ||
572 | if (t->wtcr & NPCM7XX_WTCR_WTE) { | |
573 | if (t->wtcr & NPCM7XX_WTCR_WTIF) { | |
574 | if (t->wtcr & NPCM7XX_WTCR_WTRE) { | |
575 | t->wtcr |= NPCM7XX_WTCR_WTRF; | |
576 | /* send reset signal to CLK module*/ | |
577 | qemu_irq_raise(t->reset_signal); | |
578 | } | |
579 | } else { | |
580 | t->wtcr |= NPCM7XX_WTCR_WTIF; | |
581 | if (t->wtcr & NPCM7XX_WTCR_WTIE) { | |
582 | /* send interrupt */ | |
583 | qemu_irq_raise(t->irq); | |
584 | } | |
585 | npcm7xx_watchdog_timer_reset_cycles(t, | |
586 | NPCM7XX_WATCHDOG_INTERRUPT_TO_RESET_CYCLES); | |
587 | npcm7xx_timer_start(&t->base_timer); | |
588 | } | |
589 | } | |
85fdd74f HS |
590 | } |
591 | ||
592 | static void npcm7xx_timer_hold_reset(Object *obj) | |
593 | { | |
594 | NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj); | |
595 | int i; | |
596 | ||
597 | for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) { | |
598 | qemu_irq_lower(s->timer[i].irq); | |
599 | } | |
7d378ed6 | 600 | qemu_irq_lower(s->watchdog_timer.irq); |
85fdd74f HS |
601 | } |
602 | ||
0be12dc7 | 603 | static void npcm7xx_timer_init(Object *obj) |
85fdd74f | 604 | { |
0be12dc7 HW |
605 | NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj); |
606 | DeviceState *dev = DEVICE(obj); | |
607 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); | |
85fdd74f | 608 | int i; |
7d378ed6 | 609 | NPCM7xxWatchdogTimer *w; |
85fdd74f HS |
610 | |
611 | for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) { | |
612 | NPCM7xxTimer *t = &s->timer[i]; | |
613 | t->ctrl = s; | |
7d378ed6 HW |
614 | timer_init_ns(&t->base_timer.qtimer, QEMU_CLOCK_VIRTUAL, |
615 | npcm7xx_timer_expired, t); | |
85fdd74f HS |
616 | sysbus_init_irq(sbd, &t->irq); |
617 | } | |
618 | ||
7d378ed6 HW |
619 | w = &s->watchdog_timer; |
620 | w->ctrl = s; | |
621 | timer_init_ns(&w->base_timer.qtimer, QEMU_CLOCK_VIRTUAL, | |
622 | npcm7xx_watchdog_timer_expired, w); | |
623 | sysbus_init_irq(sbd, &w->irq); | |
624 | ||
0be12dc7 | 625 | memory_region_init_io(&s->iomem, obj, &npcm7xx_timer_ops, s, |
85fdd74f HS |
626 | TYPE_NPCM7XX_TIMER, 4 * KiB); |
627 | sysbus_init_mmio(sbd, &s->iomem); | |
7d378ed6 HW |
628 | qdev_init_gpio_out_named(dev, &w->reset_signal, |
629 | NPCM7XX_WATCHDOG_RESET_GPIO_OUT, 1); | |
5ee0abed | 630 | s->clock = qdev_init_clock_in(dev, "clock", NULL, NULL, 0); |
85fdd74f HS |
631 | } |
632 | ||
7d378ed6 HW |
633 | static const VMStateDescription vmstate_npcm7xx_base_timer = { |
634 | .name = "npcm7xx-base-timer", | |
85fdd74f HS |
635 | .version_id = 0, |
636 | .minimum_version_id = 0, | |
637 | .fields = (VMStateField[]) { | |
7d378ed6 HW |
638 | VMSTATE_TIMER(qtimer, NPCM7xxBaseTimer), |
639 | VMSTATE_INT64(expires_ns, NPCM7xxBaseTimer), | |
640 | VMSTATE_INT64(remaining_ns, NPCM7xxBaseTimer), | |
641 | VMSTATE_END_OF_LIST(), | |
642 | }, | |
643 | }; | |
644 | ||
645 | static const VMStateDescription vmstate_npcm7xx_timer = { | |
646 | .name = "npcm7xx-timer", | |
647 | .version_id = 1, | |
648 | .minimum_version_id = 1, | |
649 | .fields = (VMStateField[]) { | |
650 | VMSTATE_STRUCT(base_timer, NPCM7xxTimer, | |
651 | 0, vmstate_npcm7xx_base_timer, | |
652 | NPCM7xxBaseTimer), | |
85fdd74f HS |
653 | VMSTATE_UINT32(tcsr, NPCM7xxTimer), |
654 | VMSTATE_UINT32(ticr, NPCM7xxTimer), | |
655 | VMSTATE_END_OF_LIST(), | |
656 | }, | |
657 | }; | |
658 | ||
7d378ed6 HW |
659 | static const VMStateDescription vmstate_npcm7xx_watchdog_timer = { |
660 | .name = "npcm7xx-watchdog-timer", | |
85fdd74f HS |
661 | .version_id = 0, |
662 | .minimum_version_id = 0, | |
7d378ed6 HW |
663 | .fields = (VMStateField[]) { |
664 | VMSTATE_STRUCT(base_timer, NPCM7xxWatchdogTimer, | |
665 | 0, vmstate_npcm7xx_base_timer, | |
666 | NPCM7xxBaseTimer), | |
667 | VMSTATE_UINT32(wtcr, NPCM7xxWatchdogTimer), | |
668 | VMSTATE_END_OF_LIST(), | |
669 | }, | |
670 | }; | |
671 | ||
672 | static const VMStateDescription vmstate_npcm7xx_timer_ctrl = { | |
673 | .name = "npcm7xx-timer-ctrl", | |
0be12dc7 HW |
674 | .version_id = 2, |
675 | .minimum_version_id = 2, | |
85fdd74f HS |
676 | .fields = (VMStateField[]) { |
677 | VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState), | |
0be12dc7 | 678 | VMSTATE_CLOCK(clock, NPCM7xxTimerCtrlState), |
85fdd74f HS |
679 | VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState, |
680 | NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_timer, | |
681 | NPCM7xxTimer), | |
7d378ed6 HW |
682 | VMSTATE_STRUCT(watchdog_timer, NPCM7xxTimerCtrlState, |
683 | 0, vmstate_npcm7xx_watchdog_timer, | |
684 | NPCM7xxWatchdogTimer), | |
85fdd74f HS |
685 | VMSTATE_END_OF_LIST(), |
686 | }, | |
687 | }; | |
688 | ||
689 | static void npcm7xx_timer_class_init(ObjectClass *klass, void *data) | |
690 | { | |
691 | ResettableClass *rc = RESETTABLE_CLASS(klass); | |
692 | DeviceClass *dc = DEVICE_CLASS(klass); | |
693 | ||
694 | QEMU_BUILD_BUG_ON(NPCM7XX_TIMER_REGS_END > NPCM7XX_TIMER_NR_REGS); | |
695 | ||
696 | dc->desc = "NPCM7xx Timer Controller"; | |
85fdd74f HS |
697 | dc->vmsd = &vmstate_npcm7xx_timer_ctrl; |
698 | rc->phases.enter = npcm7xx_timer_enter_reset; | |
699 | rc->phases.hold = npcm7xx_timer_hold_reset; | |
700 | } | |
701 | ||
702 | static const TypeInfo npcm7xx_timer_info = { | |
703 | .name = TYPE_NPCM7XX_TIMER, | |
704 | .parent = TYPE_SYS_BUS_DEVICE, | |
705 | .instance_size = sizeof(NPCM7xxTimerCtrlState), | |
706 | .class_init = npcm7xx_timer_class_init, | |
0be12dc7 | 707 | .instance_init = npcm7xx_timer_init, |
85fdd74f HS |
708 | }; |
709 | ||
710 | static void npcm7xx_timer_register_type(void) | |
711 | { | |
712 | type_register_static(&npcm7xx_timer_info); | |
713 | } | |
714 | type_init(npcm7xx_timer_register_type); |