xref: /qemu/hw/timer/npcm7xx_timer.c (revision 2ac88848cb03605e2fae6a035650eea461218af2)
185fdd74fSHavard Skinnemoen /*
285fdd74fSHavard Skinnemoen  * Nuvoton NPCM7xx Timer Controller
385fdd74fSHavard Skinnemoen  *
485fdd74fSHavard Skinnemoen  * Copyright 2020 Google LLC
585fdd74fSHavard Skinnemoen  *
685fdd74fSHavard Skinnemoen  * This program is free software; you can redistribute it and/or modify it
785fdd74fSHavard Skinnemoen  * under the terms of the GNU General Public License as published by the
885fdd74fSHavard Skinnemoen  * Free Software Foundation; either version 2 of the License, or
985fdd74fSHavard Skinnemoen  * (at your option) any later version.
1085fdd74fSHavard Skinnemoen  *
1185fdd74fSHavard Skinnemoen  * This program is distributed in the hope that it will be useful, but WITHOUT
1285fdd74fSHavard Skinnemoen  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1385fdd74fSHavard Skinnemoen  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
1485fdd74fSHavard Skinnemoen  * for more details.
1585fdd74fSHavard Skinnemoen  */
1685fdd74fSHavard Skinnemoen 
1785fdd74fSHavard Skinnemoen #include "qemu/osdep.h"
1885fdd74fSHavard Skinnemoen 
1985fdd74fSHavard Skinnemoen #include "hw/irq.h"
2085fdd74fSHavard Skinnemoen #include "hw/misc/npcm7xx_clk.h"
2185fdd74fSHavard Skinnemoen #include "hw/timer/npcm7xx_timer.h"
2285fdd74fSHavard Skinnemoen #include "migration/vmstate.h"
2385fdd74fSHavard Skinnemoen #include "qemu/bitops.h"
2485fdd74fSHavard Skinnemoen #include "qemu/error-report.h"
2585fdd74fSHavard Skinnemoen #include "qemu/log.h"
2685fdd74fSHavard Skinnemoen #include "qemu/module.h"
2785fdd74fSHavard Skinnemoen #include "qemu/timer.h"
2885fdd74fSHavard Skinnemoen #include "qemu/units.h"
2985fdd74fSHavard Skinnemoen #include "trace.h"
3085fdd74fSHavard Skinnemoen 
3185fdd74fSHavard Skinnemoen /* 32-bit register indices. */
3285fdd74fSHavard Skinnemoen enum NPCM7xxTimerRegisters {
3385fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TCSR0,
3485fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TCSR1,
3585fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TICR0,
3685fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TICR1,
3785fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TDR0,
3885fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TDR1,
3985fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TISR,
4085fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_WTCR,
4185fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TCSR2,
4285fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TCSR3,
4385fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TICR2,
4485fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TICR3,
4585fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TDR2,
4685fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TDR3,
4785fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TCSR4         = 0x0040 / sizeof(uint32_t),
4885fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TICR4         = 0x0048 / sizeof(uint32_t),
4985fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_TDR4          = 0x0050 / sizeof(uint32_t),
5085fdd74fSHavard Skinnemoen     NPCM7XX_TIMER_REGS_END,
5185fdd74fSHavard Skinnemoen };
5285fdd74fSHavard Skinnemoen 
5385fdd74fSHavard Skinnemoen /* Register field definitions. */
5485fdd74fSHavard Skinnemoen #define NPCM7XX_TCSR_CEN                BIT(30)
5585fdd74fSHavard Skinnemoen #define NPCM7XX_TCSR_IE                 BIT(29)
5685fdd74fSHavard Skinnemoen #define NPCM7XX_TCSR_PERIODIC           BIT(27)
5785fdd74fSHavard Skinnemoen #define NPCM7XX_TCSR_CRST               BIT(26)
5885fdd74fSHavard Skinnemoen #define NPCM7XX_TCSR_CACT               BIT(25)
5985fdd74fSHavard Skinnemoen #define NPCM7XX_TCSR_RSVD               0x01ffff00
6085fdd74fSHavard Skinnemoen #define NPCM7XX_TCSR_PRESCALE_START     0
6185fdd74fSHavard Skinnemoen #define NPCM7XX_TCSR_PRESCALE_LEN       8
6285fdd74fSHavard Skinnemoen 
6385fdd74fSHavard Skinnemoen /*
6485fdd74fSHavard Skinnemoen  * Returns the index of timer in the tc->timer array. This can be used to
6585fdd74fSHavard Skinnemoen  * locate the registers that belong to this timer.
6685fdd74fSHavard Skinnemoen  */
6785fdd74fSHavard Skinnemoen static int npcm7xx_timer_index(NPCM7xxTimerCtrlState *tc, NPCM7xxTimer *timer)
6885fdd74fSHavard Skinnemoen {
6985fdd74fSHavard Skinnemoen     int index = timer - tc->timer;
7085fdd74fSHavard Skinnemoen 
7185fdd74fSHavard Skinnemoen     g_assert(index >= 0 && index < NPCM7XX_TIMERS_PER_CTRL);
7285fdd74fSHavard Skinnemoen 
7385fdd74fSHavard Skinnemoen     return index;
7485fdd74fSHavard Skinnemoen }
7585fdd74fSHavard Skinnemoen 
7685fdd74fSHavard Skinnemoen /* Return the value by which to divide the reference clock rate. */
7785fdd74fSHavard Skinnemoen static uint32_t npcm7xx_tcsr_prescaler(uint32_t tcsr)
7885fdd74fSHavard Skinnemoen {
7985fdd74fSHavard Skinnemoen     return extract32(tcsr, NPCM7XX_TCSR_PRESCALE_START,
8085fdd74fSHavard Skinnemoen                      NPCM7XX_TCSR_PRESCALE_LEN) + 1;
8185fdd74fSHavard Skinnemoen }
8285fdd74fSHavard Skinnemoen 
8385fdd74fSHavard Skinnemoen /* Convert a timer cycle count to a time interval in nanoseconds. */
8485fdd74fSHavard Skinnemoen static int64_t npcm7xx_timer_count_to_ns(NPCM7xxTimer *t, uint32_t count)
8585fdd74fSHavard Skinnemoen {
8685fdd74fSHavard Skinnemoen     int64_t ns = count;
8785fdd74fSHavard Skinnemoen 
8885fdd74fSHavard Skinnemoen     ns *= NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ;
8985fdd74fSHavard Skinnemoen     ns *= npcm7xx_tcsr_prescaler(t->tcsr);
9085fdd74fSHavard Skinnemoen 
9185fdd74fSHavard Skinnemoen     return ns;
9285fdd74fSHavard Skinnemoen }
9385fdd74fSHavard Skinnemoen 
9485fdd74fSHavard Skinnemoen /* Convert a time interval in nanoseconds to a timer cycle count. */
9585fdd74fSHavard Skinnemoen static uint32_t npcm7xx_timer_ns_to_count(NPCM7xxTimer *t, int64_t ns)
9685fdd74fSHavard Skinnemoen {
9785fdd74fSHavard Skinnemoen     int64_t count;
9885fdd74fSHavard Skinnemoen 
9985fdd74fSHavard Skinnemoen     count = ns / (NANOSECONDS_PER_SECOND / NPCM7XX_TIMER_REF_HZ);
10085fdd74fSHavard Skinnemoen     count /= npcm7xx_tcsr_prescaler(t->tcsr);
10185fdd74fSHavard Skinnemoen 
10285fdd74fSHavard Skinnemoen     return count;
10385fdd74fSHavard Skinnemoen }
10485fdd74fSHavard Skinnemoen 
10585fdd74fSHavard Skinnemoen /*
10685fdd74fSHavard Skinnemoen  * Raise the interrupt line if there's a pending interrupt and interrupts are
10785fdd74fSHavard Skinnemoen  * enabled for this timer. If not, lower it.
10885fdd74fSHavard Skinnemoen  */
10985fdd74fSHavard Skinnemoen static void npcm7xx_timer_check_interrupt(NPCM7xxTimer *t)
11085fdd74fSHavard Skinnemoen {
11185fdd74fSHavard Skinnemoen     NPCM7xxTimerCtrlState *tc = t->ctrl;
11285fdd74fSHavard Skinnemoen     int index = npcm7xx_timer_index(tc, t);
11385fdd74fSHavard Skinnemoen     bool pending = (t->tcsr & NPCM7XX_TCSR_IE) && (tc->tisr & BIT(index));
11485fdd74fSHavard Skinnemoen 
11585fdd74fSHavard Skinnemoen     qemu_set_irq(t->irq, pending);
11685fdd74fSHavard Skinnemoen     trace_npcm7xx_timer_irq(DEVICE(tc)->canonical_path, index, pending);
11785fdd74fSHavard Skinnemoen }
11885fdd74fSHavard Skinnemoen 
11985fdd74fSHavard Skinnemoen /* Start or resume the timer. */
12085fdd74fSHavard Skinnemoen static void npcm7xx_timer_start(NPCM7xxTimer *t)
12185fdd74fSHavard Skinnemoen {
12285fdd74fSHavard Skinnemoen     int64_t now;
12385fdd74fSHavard Skinnemoen 
12485fdd74fSHavard Skinnemoen     now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
12585fdd74fSHavard Skinnemoen     t->expires_ns = now + t->remaining_ns;
12685fdd74fSHavard Skinnemoen     timer_mod(&t->qtimer, t->expires_ns);
12785fdd74fSHavard Skinnemoen }
12885fdd74fSHavard Skinnemoen 
12985fdd74fSHavard Skinnemoen /*
13085fdd74fSHavard Skinnemoen  * Called when the counter reaches zero. Sets the interrupt flag, and either
13185fdd74fSHavard Skinnemoen  * restarts or disables the timer.
13285fdd74fSHavard Skinnemoen  */
13385fdd74fSHavard Skinnemoen static void npcm7xx_timer_reached_zero(NPCM7xxTimer *t)
13485fdd74fSHavard Skinnemoen {
13585fdd74fSHavard Skinnemoen     NPCM7xxTimerCtrlState *tc = t->ctrl;
13685fdd74fSHavard Skinnemoen     int index = npcm7xx_timer_index(tc, t);
13785fdd74fSHavard Skinnemoen 
13885fdd74fSHavard Skinnemoen     tc->tisr |= BIT(index);
13985fdd74fSHavard Skinnemoen 
14085fdd74fSHavard Skinnemoen     if (t->tcsr & NPCM7XX_TCSR_PERIODIC) {
14185fdd74fSHavard Skinnemoen         t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
14285fdd74fSHavard Skinnemoen         if (t->tcsr & NPCM7XX_TCSR_CEN) {
14385fdd74fSHavard Skinnemoen             npcm7xx_timer_start(t);
14485fdd74fSHavard Skinnemoen         }
14585fdd74fSHavard Skinnemoen     } else {
14685fdd74fSHavard Skinnemoen         t->tcsr &= ~(NPCM7XX_TCSR_CEN | NPCM7XX_TCSR_CACT);
14785fdd74fSHavard Skinnemoen     }
14885fdd74fSHavard Skinnemoen 
14985fdd74fSHavard Skinnemoen     npcm7xx_timer_check_interrupt(t);
15085fdd74fSHavard Skinnemoen }
15185fdd74fSHavard Skinnemoen 
15285fdd74fSHavard Skinnemoen /* Stop counting. Record the time remaining so we can continue later. */
15385fdd74fSHavard Skinnemoen static void npcm7xx_timer_pause(NPCM7xxTimer *t)
15485fdd74fSHavard Skinnemoen {
15585fdd74fSHavard Skinnemoen     int64_t now;
15685fdd74fSHavard Skinnemoen 
15785fdd74fSHavard Skinnemoen     timer_del(&t->qtimer);
15885fdd74fSHavard Skinnemoen     now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
15985fdd74fSHavard Skinnemoen     t->remaining_ns = t->expires_ns - now;
16085fdd74fSHavard Skinnemoen }
16185fdd74fSHavard Skinnemoen 
16285fdd74fSHavard Skinnemoen /*
16385fdd74fSHavard Skinnemoen  * Restart the timer from its initial value. If the timer was enabled and stays
16485fdd74fSHavard Skinnemoen  * enabled, adjust the QEMU timer according to the new count. If the timer is
16585fdd74fSHavard Skinnemoen  * transitioning from disabled to enabled, the caller is expected to start the
16685fdd74fSHavard Skinnemoen  * timer later.
16785fdd74fSHavard Skinnemoen  */
16885fdd74fSHavard Skinnemoen static void npcm7xx_timer_restart(NPCM7xxTimer *t, uint32_t old_tcsr)
16985fdd74fSHavard Skinnemoen {
17085fdd74fSHavard Skinnemoen     t->remaining_ns = npcm7xx_timer_count_to_ns(t, t->ticr);
17185fdd74fSHavard Skinnemoen 
17285fdd74fSHavard Skinnemoen     if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
17385fdd74fSHavard Skinnemoen         npcm7xx_timer_start(t);
17485fdd74fSHavard Skinnemoen     }
17585fdd74fSHavard Skinnemoen }
17685fdd74fSHavard Skinnemoen 
17785fdd74fSHavard Skinnemoen /* Register read and write handlers */
17885fdd74fSHavard Skinnemoen 
17985fdd74fSHavard Skinnemoen static uint32_t npcm7xx_timer_read_tdr(NPCM7xxTimer *t)
18085fdd74fSHavard Skinnemoen {
18185fdd74fSHavard Skinnemoen     if (t->tcsr & NPCM7XX_TCSR_CEN) {
18285fdd74fSHavard Skinnemoen         int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
18385fdd74fSHavard Skinnemoen 
18485fdd74fSHavard Skinnemoen         return npcm7xx_timer_ns_to_count(t, t->expires_ns - now);
18585fdd74fSHavard Skinnemoen     }
18685fdd74fSHavard Skinnemoen 
18785fdd74fSHavard Skinnemoen     return npcm7xx_timer_ns_to_count(t, t->remaining_ns);
18885fdd74fSHavard Skinnemoen }
18985fdd74fSHavard Skinnemoen 
19085fdd74fSHavard Skinnemoen static void npcm7xx_timer_write_tcsr(NPCM7xxTimer *t, uint32_t new_tcsr)
19185fdd74fSHavard Skinnemoen {
19285fdd74fSHavard Skinnemoen     uint32_t old_tcsr = t->tcsr;
19385fdd74fSHavard Skinnemoen     uint32_t tdr;
19485fdd74fSHavard Skinnemoen 
19585fdd74fSHavard Skinnemoen     if (new_tcsr & NPCM7XX_TCSR_RSVD) {
19685fdd74fSHavard Skinnemoen         qemu_log_mask(LOG_GUEST_ERROR, "%s: reserved bits in 0x%08x ignored\n",
19785fdd74fSHavard Skinnemoen                       __func__, new_tcsr);
19885fdd74fSHavard Skinnemoen         new_tcsr &= ~NPCM7XX_TCSR_RSVD;
19985fdd74fSHavard Skinnemoen     }
20085fdd74fSHavard Skinnemoen     if (new_tcsr & NPCM7XX_TCSR_CACT) {
20185fdd74fSHavard Skinnemoen         qemu_log_mask(LOG_GUEST_ERROR, "%s: read-only bits in 0x%08x ignored\n",
20285fdd74fSHavard Skinnemoen                       __func__, new_tcsr);
20385fdd74fSHavard Skinnemoen         new_tcsr &= ~NPCM7XX_TCSR_CACT;
20485fdd74fSHavard Skinnemoen     }
20585fdd74fSHavard Skinnemoen     if ((new_tcsr & NPCM7XX_TCSR_CRST) && (new_tcsr & NPCM7XX_TCSR_CEN)) {
20685fdd74fSHavard Skinnemoen         qemu_log_mask(LOG_GUEST_ERROR,
20785fdd74fSHavard Skinnemoen                       "%s: both CRST and CEN set; ignoring CEN.\n",
20885fdd74fSHavard Skinnemoen                       __func__);
20985fdd74fSHavard Skinnemoen         new_tcsr &= ~NPCM7XX_TCSR_CEN;
21085fdd74fSHavard Skinnemoen     }
21185fdd74fSHavard Skinnemoen 
21285fdd74fSHavard Skinnemoen     /* Calculate the value of TDR before potentially changing the prescaler. */
21385fdd74fSHavard Skinnemoen     tdr = npcm7xx_timer_read_tdr(t);
21485fdd74fSHavard Skinnemoen 
21585fdd74fSHavard Skinnemoen     t->tcsr = (t->tcsr & NPCM7XX_TCSR_CACT) | new_tcsr;
21685fdd74fSHavard Skinnemoen 
21785fdd74fSHavard Skinnemoen     if (npcm7xx_tcsr_prescaler(old_tcsr) != npcm7xx_tcsr_prescaler(new_tcsr)) {
21885fdd74fSHavard Skinnemoen         /* Recalculate time remaining based on the current TDR value. */
21985fdd74fSHavard Skinnemoen         t->remaining_ns = npcm7xx_timer_count_to_ns(t, tdr);
22085fdd74fSHavard Skinnemoen         if (old_tcsr & t->tcsr & NPCM7XX_TCSR_CEN) {
22185fdd74fSHavard Skinnemoen             npcm7xx_timer_start(t);
22285fdd74fSHavard Skinnemoen         }
22385fdd74fSHavard Skinnemoen     }
22485fdd74fSHavard Skinnemoen 
22585fdd74fSHavard Skinnemoen     if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_IE) {
22685fdd74fSHavard Skinnemoen         npcm7xx_timer_check_interrupt(t);
22785fdd74fSHavard Skinnemoen     }
22885fdd74fSHavard Skinnemoen     if (new_tcsr & NPCM7XX_TCSR_CRST) {
22985fdd74fSHavard Skinnemoen         npcm7xx_timer_restart(t, old_tcsr);
23085fdd74fSHavard Skinnemoen         t->tcsr &= ~NPCM7XX_TCSR_CRST;
23185fdd74fSHavard Skinnemoen     }
23285fdd74fSHavard Skinnemoen     if ((old_tcsr ^ new_tcsr) & NPCM7XX_TCSR_CEN) {
23385fdd74fSHavard Skinnemoen         if (new_tcsr & NPCM7XX_TCSR_CEN) {
23485fdd74fSHavard Skinnemoen             t->tcsr |= NPCM7XX_TCSR_CACT;
23585fdd74fSHavard Skinnemoen             npcm7xx_timer_start(t);
23685fdd74fSHavard Skinnemoen         } else {
23785fdd74fSHavard Skinnemoen             t->tcsr &= ~NPCM7XX_TCSR_CACT;
23885fdd74fSHavard Skinnemoen             npcm7xx_timer_pause(t);
239*2ac88848SHavard Skinnemoen             if (t->remaining_ns <= 0) {
240*2ac88848SHavard Skinnemoen                 npcm7xx_timer_reached_zero(t);
241*2ac88848SHavard Skinnemoen             }
24285fdd74fSHavard Skinnemoen         }
24385fdd74fSHavard Skinnemoen     }
24485fdd74fSHavard Skinnemoen }
24585fdd74fSHavard Skinnemoen 
24685fdd74fSHavard Skinnemoen static void npcm7xx_timer_write_ticr(NPCM7xxTimer *t, uint32_t new_ticr)
24785fdd74fSHavard Skinnemoen {
24885fdd74fSHavard Skinnemoen     t->ticr = new_ticr;
24985fdd74fSHavard Skinnemoen 
25085fdd74fSHavard Skinnemoen     npcm7xx_timer_restart(t, t->tcsr);
25185fdd74fSHavard Skinnemoen }
25285fdd74fSHavard Skinnemoen 
25385fdd74fSHavard Skinnemoen static void npcm7xx_timer_write_tisr(NPCM7xxTimerCtrlState *s, uint32_t value)
25485fdd74fSHavard Skinnemoen {
25585fdd74fSHavard Skinnemoen     int i;
25685fdd74fSHavard Skinnemoen 
25785fdd74fSHavard Skinnemoen     s->tisr &= ~value;
25885fdd74fSHavard Skinnemoen     for (i = 0; i < ARRAY_SIZE(s->timer); i++) {
25985fdd74fSHavard Skinnemoen         if (value & (1U << i)) {
26085fdd74fSHavard Skinnemoen             npcm7xx_timer_check_interrupt(&s->timer[i]);
26185fdd74fSHavard Skinnemoen         }
26285fdd74fSHavard Skinnemoen     }
26385fdd74fSHavard Skinnemoen }
26485fdd74fSHavard Skinnemoen 
26585fdd74fSHavard Skinnemoen static hwaddr npcm7xx_tcsr_index(hwaddr reg)
26685fdd74fSHavard Skinnemoen {
26785fdd74fSHavard Skinnemoen     switch (reg) {
26885fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR0:
26985fdd74fSHavard Skinnemoen         return 0;
27085fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR1:
27185fdd74fSHavard Skinnemoen         return 1;
27285fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR2:
27385fdd74fSHavard Skinnemoen         return 2;
27485fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR3:
27585fdd74fSHavard Skinnemoen         return 3;
27685fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR4:
27785fdd74fSHavard Skinnemoen         return 4;
27885fdd74fSHavard Skinnemoen     default:
27985fdd74fSHavard Skinnemoen         g_assert_not_reached();
28085fdd74fSHavard Skinnemoen     }
28185fdd74fSHavard Skinnemoen }
28285fdd74fSHavard Skinnemoen 
28385fdd74fSHavard Skinnemoen static hwaddr npcm7xx_ticr_index(hwaddr reg)
28485fdd74fSHavard Skinnemoen {
28585fdd74fSHavard Skinnemoen     switch (reg) {
28685fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR0:
28785fdd74fSHavard Skinnemoen         return 0;
28885fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR1:
28985fdd74fSHavard Skinnemoen         return 1;
29085fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR2:
29185fdd74fSHavard Skinnemoen         return 2;
29285fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR3:
29385fdd74fSHavard Skinnemoen         return 3;
29485fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR4:
29585fdd74fSHavard Skinnemoen         return 4;
29685fdd74fSHavard Skinnemoen     default:
29785fdd74fSHavard Skinnemoen         g_assert_not_reached();
29885fdd74fSHavard Skinnemoen     }
29985fdd74fSHavard Skinnemoen }
30085fdd74fSHavard Skinnemoen 
30185fdd74fSHavard Skinnemoen static hwaddr npcm7xx_tdr_index(hwaddr reg)
30285fdd74fSHavard Skinnemoen {
30385fdd74fSHavard Skinnemoen     switch (reg) {
30485fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR0:
30585fdd74fSHavard Skinnemoen         return 0;
30685fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR1:
30785fdd74fSHavard Skinnemoen         return 1;
30885fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR2:
30985fdd74fSHavard Skinnemoen         return 2;
31085fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR3:
31185fdd74fSHavard Skinnemoen         return 3;
31285fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR4:
31385fdd74fSHavard Skinnemoen         return 4;
31485fdd74fSHavard Skinnemoen     default:
31585fdd74fSHavard Skinnemoen         g_assert_not_reached();
31685fdd74fSHavard Skinnemoen     }
31785fdd74fSHavard Skinnemoen }
31885fdd74fSHavard Skinnemoen 
31985fdd74fSHavard Skinnemoen static uint64_t npcm7xx_timer_read(void *opaque, hwaddr offset, unsigned size)
32085fdd74fSHavard Skinnemoen {
32185fdd74fSHavard Skinnemoen     NPCM7xxTimerCtrlState *s = opaque;
32285fdd74fSHavard Skinnemoen     uint64_t value = 0;
32385fdd74fSHavard Skinnemoen     hwaddr reg;
32485fdd74fSHavard Skinnemoen 
32585fdd74fSHavard Skinnemoen     reg = offset / sizeof(uint32_t);
32685fdd74fSHavard Skinnemoen     switch (reg) {
32785fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR0:
32885fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR1:
32985fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR2:
33085fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR3:
33185fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR4:
33285fdd74fSHavard Skinnemoen         value = s->timer[npcm7xx_tcsr_index(reg)].tcsr;
33385fdd74fSHavard Skinnemoen         break;
33485fdd74fSHavard Skinnemoen 
33585fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR0:
33685fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR1:
33785fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR2:
33885fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR3:
33985fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR4:
34085fdd74fSHavard Skinnemoen         value = s->timer[npcm7xx_ticr_index(reg)].ticr;
34185fdd74fSHavard Skinnemoen         break;
34285fdd74fSHavard Skinnemoen 
34385fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR0:
34485fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR1:
34585fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR2:
34685fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR3:
34785fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR4:
34885fdd74fSHavard Skinnemoen         value = npcm7xx_timer_read_tdr(&s->timer[npcm7xx_tdr_index(reg)]);
34985fdd74fSHavard Skinnemoen         break;
35085fdd74fSHavard Skinnemoen 
35185fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TISR:
35285fdd74fSHavard Skinnemoen         value = s->tisr;
35385fdd74fSHavard Skinnemoen         break;
35485fdd74fSHavard Skinnemoen 
35585fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_WTCR:
35685fdd74fSHavard Skinnemoen         value = s->wtcr;
35785fdd74fSHavard Skinnemoen         break;
35885fdd74fSHavard Skinnemoen 
35985fdd74fSHavard Skinnemoen     default:
36085fdd74fSHavard Skinnemoen         qemu_log_mask(LOG_GUEST_ERROR,
36185fdd74fSHavard Skinnemoen                       "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
36285fdd74fSHavard Skinnemoen                       __func__, offset);
36385fdd74fSHavard Skinnemoen         break;
36485fdd74fSHavard Skinnemoen     }
36585fdd74fSHavard Skinnemoen 
36685fdd74fSHavard Skinnemoen     trace_npcm7xx_timer_read(DEVICE(s)->canonical_path, offset, value);
36785fdd74fSHavard Skinnemoen 
36885fdd74fSHavard Skinnemoen     return value;
36985fdd74fSHavard Skinnemoen }
37085fdd74fSHavard Skinnemoen 
37185fdd74fSHavard Skinnemoen static void npcm7xx_timer_write(void *opaque, hwaddr offset,
37285fdd74fSHavard Skinnemoen                                 uint64_t v, unsigned size)
37385fdd74fSHavard Skinnemoen {
37485fdd74fSHavard Skinnemoen     uint32_t reg = offset / sizeof(uint32_t);
37585fdd74fSHavard Skinnemoen     NPCM7xxTimerCtrlState *s = opaque;
37685fdd74fSHavard Skinnemoen     uint32_t value = v;
37785fdd74fSHavard Skinnemoen 
37885fdd74fSHavard Skinnemoen     trace_npcm7xx_timer_write(DEVICE(s)->canonical_path, offset, value);
37985fdd74fSHavard Skinnemoen 
38085fdd74fSHavard Skinnemoen     switch (reg) {
38185fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR0:
38285fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR1:
38385fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR2:
38485fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR3:
38585fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TCSR4:
38685fdd74fSHavard Skinnemoen         npcm7xx_timer_write_tcsr(&s->timer[npcm7xx_tcsr_index(reg)], value);
38785fdd74fSHavard Skinnemoen         return;
38885fdd74fSHavard Skinnemoen 
38985fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR0:
39085fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR1:
39185fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR2:
39285fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR3:
39385fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TICR4:
39485fdd74fSHavard Skinnemoen         npcm7xx_timer_write_ticr(&s->timer[npcm7xx_ticr_index(reg)], value);
39585fdd74fSHavard Skinnemoen         return;
39685fdd74fSHavard Skinnemoen 
39785fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR0:
39885fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR1:
39985fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR2:
40085fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR3:
40185fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TDR4:
40285fdd74fSHavard Skinnemoen         qemu_log_mask(LOG_GUEST_ERROR,
40385fdd74fSHavard Skinnemoen                       "%s: register @ 0x%04" HWADDR_PRIx " is read-only\n",
40485fdd74fSHavard Skinnemoen                       __func__, offset);
40585fdd74fSHavard Skinnemoen         return;
40685fdd74fSHavard Skinnemoen 
40785fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_TISR:
40885fdd74fSHavard Skinnemoen         npcm7xx_timer_write_tisr(s, value);
40985fdd74fSHavard Skinnemoen         return;
41085fdd74fSHavard Skinnemoen 
41185fdd74fSHavard Skinnemoen     case NPCM7XX_TIMER_WTCR:
41285fdd74fSHavard Skinnemoen         qemu_log_mask(LOG_UNIMP, "%s: WTCR write not implemented: 0x%08x\n",
41385fdd74fSHavard Skinnemoen                       __func__, value);
41485fdd74fSHavard Skinnemoen         return;
41585fdd74fSHavard Skinnemoen     }
41685fdd74fSHavard Skinnemoen 
41785fdd74fSHavard Skinnemoen     qemu_log_mask(LOG_GUEST_ERROR,
41885fdd74fSHavard Skinnemoen                   "%s: invalid offset 0x%04" HWADDR_PRIx "\n",
41985fdd74fSHavard Skinnemoen                   __func__, offset);
42085fdd74fSHavard Skinnemoen }
42185fdd74fSHavard Skinnemoen 
42285fdd74fSHavard Skinnemoen static const struct MemoryRegionOps npcm7xx_timer_ops = {
42385fdd74fSHavard Skinnemoen     .read       = npcm7xx_timer_read,
42485fdd74fSHavard Skinnemoen     .write      = npcm7xx_timer_write,
42585fdd74fSHavard Skinnemoen     .endianness = DEVICE_LITTLE_ENDIAN,
42685fdd74fSHavard Skinnemoen     .valid      = {
42785fdd74fSHavard Skinnemoen         .min_access_size        = 4,
42885fdd74fSHavard Skinnemoen         .max_access_size        = 4,
42985fdd74fSHavard Skinnemoen         .unaligned              = false,
43085fdd74fSHavard Skinnemoen     },
43185fdd74fSHavard Skinnemoen };
43285fdd74fSHavard Skinnemoen 
43385fdd74fSHavard Skinnemoen /* Called when the QEMU timer expires. */
43485fdd74fSHavard Skinnemoen static void npcm7xx_timer_expired(void *opaque)
43585fdd74fSHavard Skinnemoen {
43685fdd74fSHavard Skinnemoen     NPCM7xxTimer *t = opaque;
43785fdd74fSHavard Skinnemoen 
43885fdd74fSHavard Skinnemoen     if (t->tcsr & NPCM7XX_TCSR_CEN) {
43985fdd74fSHavard Skinnemoen         npcm7xx_timer_reached_zero(t);
44085fdd74fSHavard Skinnemoen     }
44185fdd74fSHavard Skinnemoen }
44285fdd74fSHavard Skinnemoen 
44385fdd74fSHavard Skinnemoen static void npcm7xx_timer_enter_reset(Object *obj, ResetType type)
44485fdd74fSHavard Skinnemoen {
44585fdd74fSHavard Skinnemoen     NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj);
44685fdd74fSHavard Skinnemoen     int i;
44785fdd74fSHavard Skinnemoen 
44885fdd74fSHavard Skinnemoen     for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
44985fdd74fSHavard Skinnemoen         NPCM7xxTimer *t = &s->timer[i];
45085fdd74fSHavard Skinnemoen 
45185fdd74fSHavard Skinnemoen         timer_del(&t->qtimer);
45285fdd74fSHavard Skinnemoen         t->expires_ns = 0;
45385fdd74fSHavard Skinnemoen         t->remaining_ns = 0;
45485fdd74fSHavard Skinnemoen         t->tcsr = 0x00000005;
45585fdd74fSHavard Skinnemoen         t->ticr = 0x00000000;
45685fdd74fSHavard Skinnemoen     }
45785fdd74fSHavard Skinnemoen 
45885fdd74fSHavard Skinnemoen     s->tisr = 0x00000000;
45985fdd74fSHavard Skinnemoen     s->wtcr = 0x00000400;
46085fdd74fSHavard Skinnemoen }
46185fdd74fSHavard Skinnemoen 
46285fdd74fSHavard Skinnemoen static void npcm7xx_timer_hold_reset(Object *obj)
46385fdd74fSHavard Skinnemoen {
46485fdd74fSHavard Skinnemoen     NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(obj);
46585fdd74fSHavard Skinnemoen     int i;
46685fdd74fSHavard Skinnemoen 
46785fdd74fSHavard Skinnemoen     for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
46885fdd74fSHavard Skinnemoen         qemu_irq_lower(s->timer[i].irq);
46985fdd74fSHavard Skinnemoen     }
47085fdd74fSHavard Skinnemoen }
47185fdd74fSHavard Skinnemoen 
47285fdd74fSHavard Skinnemoen static void npcm7xx_timer_realize(DeviceState *dev, Error **errp)
47385fdd74fSHavard Skinnemoen {
47485fdd74fSHavard Skinnemoen     NPCM7xxTimerCtrlState *s = NPCM7XX_TIMER(dev);
47585fdd74fSHavard Skinnemoen     SysBusDevice *sbd = &s->parent;
47685fdd74fSHavard Skinnemoen     int i;
47785fdd74fSHavard Skinnemoen 
47885fdd74fSHavard Skinnemoen     for (i = 0; i < NPCM7XX_TIMERS_PER_CTRL; i++) {
47985fdd74fSHavard Skinnemoen         NPCM7xxTimer *t = &s->timer[i];
48085fdd74fSHavard Skinnemoen         t->ctrl = s;
48185fdd74fSHavard Skinnemoen         timer_init_ns(&t->qtimer, QEMU_CLOCK_VIRTUAL, npcm7xx_timer_expired, t);
48285fdd74fSHavard Skinnemoen         sysbus_init_irq(sbd, &t->irq);
48385fdd74fSHavard Skinnemoen     }
48485fdd74fSHavard Skinnemoen 
48585fdd74fSHavard Skinnemoen     memory_region_init_io(&s->iomem, OBJECT(s), &npcm7xx_timer_ops, s,
48685fdd74fSHavard Skinnemoen                           TYPE_NPCM7XX_TIMER, 4 * KiB);
48785fdd74fSHavard Skinnemoen     sysbus_init_mmio(sbd, &s->iomem);
48885fdd74fSHavard Skinnemoen }
48985fdd74fSHavard Skinnemoen 
49085fdd74fSHavard Skinnemoen static const VMStateDescription vmstate_npcm7xx_timer = {
49185fdd74fSHavard Skinnemoen     .name = "npcm7xx-timer",
49285fdd74fSHavard Skinnemoen     .version_id = 0,
49385fdd74fSHavard Skinnemoen     .minimum_version_id = 0,
49485fdd74fSHavard Skinnemoen     .fields = (VMStateField[]) {
49585fdd74fSHavard Skinnemoen         VMSTATE_TIMER(qtimer, NPCM7xxTimer),
49685fdd74fSHavard Skinnemoen         VMSTATE_INT64(expires_ns, NPCM7xxTimer),
49785fdd74fSHavard Skinnemoen         VMSTATE_INT64(remaining_ns, NPCM7xxTimer),
49885fdd74fSHavard Skinnemoen         VMSTATE_UINT32(tcsr, NPCM7xxTimer),
49985fdd74fSHavard Skinnemoen         VMSTATE_UINT32(ticr, NPCM7xxTimer),
50085fdd74fSHavard Skinnemoen         VMSTATE_END_OF_LIST(),
50185fdd74fSHavard Skinnemoen     },
50285fdd74fSHavard Skinnemoen };
50385fdd74fSHavard Skinnemoen 
50485fdd74fSHavard Skinnemoen static const VMStateDescription vmstate_npcm7xx_timer_ctrl = {
50585fdd74fSHavard Skinnemoen     .name = "npcm7xx-timer-ctrl",
50685fdd74fSHavard Skinnemoen     .version_id = 0,
50785fdd74fSHavard Skinnemoen     .minimum_version_id = 0,
50885fdd74fSHavard Skinnemoen     .fields = (VMStateField[]) {
50985fdd74fSHavard Skinnemoen         VMSTATE_UINT32(tisr, NPCM7xxTimerCtrlState),
51085fdd74fSHavard Skinnemoen         VMSTATE_UINT32(wtcr, NPCM7xxTimerCtrlState),
51185fdd74fSHavard Skinnemoen         VMSTATE_STRUCT_ARRAY(timer, NPCM7xxTimerCtrlState,
51285fdd74fSHavard Skinnemoen                              NPCM7XX_TIMERS_PER_CTRL, 0, vmstate_npcm7xx_timer,
51385fdd74fSHavard Skinnemoen                              NPCM7xxTimer),
51485fdd74fSHavard Skinnemoen         VMSTATE_END_OF_LIST(),
51585fdd74fSHavard Skinnemoen     },
51685fdd74fSHavard Skinnemoen };
51785fdd74fSHavard Skinnemoen 
51885fdd74fSHavard Skinnemoen static void npcm7xx_timer_class_init(ObjectClass *klass, void *data)
51985fdd74fSHavard Skinnemoen {
52085fdd74fSHavard Skinnemoen     ResettableClass *rc = RESETTABLE_CLASS(klass);
52185fdd74fSHavard Skinnemoen     DeviceClass *dc = DEVICE_CLASS(klass);
52285fdd74fSHavard Skinnemoen 
52385fdd74fSHavard Skinnemoen     QEMU_BUILD_BUG_ON(NPCM7XX_TIMER_REGS_END > NPCM7XX_TIMER_NR_REGS);
52485fdd74fSHavard Skinnemoen 
52585fdd74fSHavard Skinnemoen     dc->desc = "NPCM7xx Timer Controller";
52685fdd74fSHavard Skinnemoen     dc->realize = npcm7xx_timer_realize;
52785fdd74fSHavard Skinnemoen     dc->vmsd = &vmstate_npcm7xx_timer_ctrl;
52885fdd74fSHavard Skinnemoen     rc->phases.enter = npcm7xx_timer_enter_reset;
52985fdd74fSHavard Skinnemoen     rc->phases.hold = npcm7xx_timer_hold_reset;
53085fdd74fSHavard Skinnemoen }
53185fdd74fSHavard Skinnemoen 
53285fdd74fSHavard Skinnemoen static const TypeInfo npcm7xx_timer_info = {
53385fdd74fSHavard Skinnemoen     .name               = TYPE_NPCM7XX_TIMER,
53485fdd74fSHavard Skinnemoen     .parent             = TYPE_SYS_BUS_DEVICE,
53585fdd74fSHavard Skinnemoen     .instance_size      = sizeof(NPCM7xxTimerCtrlState),
53685fdd74fSHavard Skinnemoen     .class_init         = npcm7xx_timer_class_init,
53785fdd74fSHavard Skinnemoen };
53885fdd74fSHavard Skinnemoen 
53985fdd74fSHavard Skinnemoen static void npcm7xx_timer_register_type(void)
54085fdd74fSHavard Skinnemoen {
54185fdd74fSHavard Skinnemoen     type_register_static(&npcm7xx_timer_info);
54285fdd74fSHavard Skinnemoen }
54385fdd74fSHavard Skinnemoen type_init(npcm7xx_timer_register_type);
544