xref: /openbmc/qemu/hw/timer/ibex_timer.c (revision dda94e5c66e4c48c3709acf5532c295a80845730)
1df41cbd6SAlistair Francis /*
2df41cbd6SAlistair Francis  * QEMU lowRISC Ibex Timer device
3df41cbd6SAlistair Francis  *
4df41cbd6SAlistair Francis  * Copyright (c) 2021 Western Digital
5df41cbd6SAlistair Francis  *
6df41cbd6SAlistair Francis  * For details check the documentation here:
7df41cbd6SAlistair Francis  *    https://docs.opentitan.org/hw/ip/rv_timer/doc/
8df41cbd6SAlistair Francis  *
9df41cbd6SAlistair Francis  * Permission is hereby granted, free of charge, to any person obtaining a copy
10df41cbd6SAlistair Francis  * of this software and associated documentation files (the "Software"), to deal
11df41cbd6SAlistair Francis  * in the Software without restriction, including without limitation the rights
12df41cbd6SAlistair Francis  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13df41cbd6SAlistair Francis  * copies of the Software, and to permit persons to whom the Software is
14df41cbd6SAlistair Francis  * furnished to do so, subject to the following conditions:
15df41cbd6SAlistair Francis  *
16df41cbd6SAlistair Francis  * The above copyright notice and this permission notice shall be included in
17df41cbd6SAlistair Francis  * all copies or substantial portions of the Software.
18df41cbd6SAlistair Francis  *
19df41cbd6SAlistair Francis  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20df41cbd6SAlistair Francis  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21df41cbd6SAlistair Francis  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
22df41cbd6SAlistair Francis  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23df41cbd6SAlistair Francis  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24df41cbd6SAlistair Francis  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25df41cbd6SAlistair Francis  * THE SOFTWARE.
26df41cbd6SAlistair Francis  */
27df41cbd6SAlistair Francis 
28df41cbd6SAlistair Francis #include "qemu/osdep.h"
29df41cbd6SAlistair Francis #include "qemu/log.h"
30df41cbd6SAlistair Francis #include "qemu/timer.h"
31df41cbd6SAlistair Francis #include "hw/timer/ibex_timer.h"
32df41cbd6SAlistair Francis #include "hw/irq.h"
33df41cbd6SAlistair Francis #include "hw/qdev-properties.h"
34df41cbd6SAlistair Francis #include "target/riscv/cpu.h"
35df41cbd6SAlistair Francis #include "migration/vmstate.h"
36df41cbd6SAlistair Francis 
37*dda94e5cSWilfred Mallawa REG32(ALERT_TEST, 0x00)
38*dda94e5cSWilfred Mallawa     FIELD(ALERT_TEST, FATAL_FAULT, 0, 1)
39*dda94e5cSWilfred Mallawa REG32(CTRL, 0x04)
40df41cbd6SAlistair Francis     FIELD(CTRL, ACTIVE, 0, 1)
41df41cbd6SAlistair Francis REG32(CFG0, 0x100)
42df41cbd6SAlistair Francis     FIELD(CFG0, PRESCALE, 0, 12)
43df41cbd6SAlistair Francis     FIELD(CFG0, STEP, 16, 8)
44df41cbd6SAlistair Francis REG32(LOWER0, 0x104)
45df41cbd6SAlistair Francis REG32(UPPER0, 0x108)
46df41cbd6SAlistair Francis REG32(COMPARE_LOWER0, 0x10C)
47df41cbd6SAlistair Francis REG32(COMPARE_UPPER0, 0x110)
48df41cbd6SAlistair Francis REG32(INTR_ENABLE, 0x114)
49df41cbd6SAlistair Francis     FIELD(INTR_ENABLE, IE_0, 0, 1)
50df41cbd6SAlistair Francis REG32(INTR_STATE, 0x118)
51df41cbd6SAlistair Francis     FIELD(INTR_STATE, IS_0, 0, 1)
52df41cbd6SAlistair Francis REG32(INTR_TEST, 0x11C)
53df41cbd6SAlistair Francis     FIELD(INTR_TEST, T_0, 0, 1)
54df41cbd6SAlistair Francis 
55df41cbd6SAlistair Francis static uint64_t cpu_riscv_read_rtc(uint32_t timebase_freq)
56df41cbd6SAlistair Francis {
57df41cbd6SAlistair Francis     return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
58df41cbd6SAlistair Francis                     timebase_freq, NANOSECONDS_PER_SECOND);
59df41cbd6SAlistair Francis }
60df41cbd6SAlistair Francis 
61df41cbd6SAlistair Francis static void ibex_timer_update_irqs(IbexTimerState *s)
62df41cbd6SAlistair Francis {
63df41cbd6SAlistair Francis     CPUState *cs = qemu_get_cpu(0);
64df41cbd6SAlistair Francis     RISCVCPU *cpu = RISCV_CPU(cs);
65df41cbd6SAlistair Francis     uint64_t value = s->timer_compare_lower0 |
66df41cbd6SAlistair Francis                          ((uint64_t)s->timer_compare_upper0 << 32);
67df41cbd6SAlistair Francis     uint64_t next, diff;
68df41cbd6SAlistair Francis     uint64_t now = cpu_riscv_read_rtc(s->timebase_freq);
69df41cbd6SAlistair Francis 
70df41cbd6SAlistair Francis     if (!(s->timer_ctrl & R_CTRL_ACTIVE_MASK)) {
71df41cbd6SAlistair Francis         /* Timer isn't active */
72df41cbd6SAlistair Francis         return;
73df41cbd6SAlistair Francis     }
74df41cbd6SAlistair Francis 
75df41cbd6SAlistair Francis     /* Update the CPUs mtimecmp */
76df41cbd6SAlistair Francis     cpu->env.timecmp = value;
77df41cbd6SAlistair Francis 
78df41cbd6SAlistair Francis     if (cpu->env.timecmp <= now) {
79df41cbd6SAlistair Francis         /*
80df41cbd6SAlistair Francis          * If the mtimecmp was in the past raise the interrupt now.
81df41cbd6SAlistair Francis          */
8257a3a622SAlistair Francis         qemu_irq_raise(s->m_timer_irq);
83df41cbd6SAlistair Francis         if (s->timer_intr_enable & R_INTR_ENABLE_IE_0_MASK) {
84df41cbd6SAlistair Francis             s->timer_intr_state |= R_INTR_STATE_IS_0_MASK;
85df41cbd6SAlistair Francis             qemu_set_irq(s->irq, true);
86df41cbd6SAlistair Francis         }
87df41cbd6SAlistair Francis         return;
88df41cbd6SAlistair Francis     }
89df41cbd6SAlistair Francis 
90df41cbd6SAlistair Francis     /* Setup a timer to trigger the interrupt in the future */
9157a3a622SAlistair Francis     qemu_irq_lower(s->m_timer_irq);
92df41cbd6SAlistair Francis     qemu_set_irq(s->irq, false);
93df41cbd6SAlistair Francis 
94df41cbd6SAlistair Francis     diff = cpu->env.timecmp - now;
95df41cbd6SAlistair Francis     next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
96df41cbd6SAlistair Francis                                  muldiv64(diff,
97df41cbd6SAlistair Francis                                           NANOSECONDS_PER_SECOND,
98df41cbd6SAlistair Francis                                           s->timebase_freq);
99df41cbd6SAlistair Francis 
100df41cbd6SAlistair Francis     if (next < qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL)) {
101df41cbd6SAlistair Francis         /* We overflowed the timer, just set it as large as we can */
102df41cbd6SAlistair Francis         timer_mod(cpu->env.timer, 0x7FFFFFFFFFFFFFFF);
103df41cbd6SAlistair Francis     } else {
104df41cbd6SAlistair Francis         timer_mod(cpu->env.timer, next);
105df41cbd6SAlistair Francis     }
106df41cbd6SAlistair Francis }
107df41cbd6SAlistair Francis 
108df41cbd6SAlistair Francis static void ibex_timer_cb(void *opaque)
109df41cbd6SAlistair Francis {
110df41cbd6SAlistair Francis     IbexTimerState *s = opaque;
111df41cbd6SAlistair Francis 
11257a3a622SAlistair Francis     qemu_irq_raise(s->m_timer_irq);
113df41cbd6SAlistair Francis     if (s->timer_intr_enable & R_INTR_ENABLE_IE_0_MASK) {
114df41cbd6SAlistair Francis         s->timer_intr_state |= R_INTR_STATE_IS_0_MASK;
115df41cbd6SAlistair Francis         qemu_set_irq(s->irq, true);
116df41cbd6SAlistair Francis     }
117df41cbd6SAlistair Francis }
118df41cbd6SAlistair Francis 
119df41cbd6SAlistair Francis static void ibex_timer_reset(DeviceState *dev)
120df41cbd6SAlistair Francis {
121df41cbd6SAlistair Francis     IbexTimerState *s = IBEX_TIMER(dev);
122df41cbd6SAlistair Francis 
123df41cbd6SAlistair Francis     CPUState *cpu = qemu_get_cpu(0);
124df41cbd6SAlistair Francis     CPURISCVState *env = cpu->env_ptr;
125df41cbd6SAlistair Francis     env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
126df41cbd6SAlistair Francis                               &ibex_timer_cb, s);
127df41cbd6SAlistair Francis     env->timecmp = 0;
128df41cbd6SAlistair Francis 
129df41cbd6SAlistair Francis     s->timer_ctrl = 0x00000000;
130df41cbd6SAlistair Francis     s->timer_cfg0 = 0x00010000;
131df41cbd6SAlistair Francis     s->timer_compare_lower0 = 0xFFFFFFFF;
132df41cbd6SAlistair Francis     s->timer_compare_upper0 = 0xFFFFFFFF;
133df41cbd6SAlistair Francis     s->timer_intr_enable = 0x00000000;
134df41cbd6SAlistair Francis     s->timer_intr_state = 0x00000000;
135df41cbd6SAlistair Francis 
136df41cbd6SAlistair Francis     ibex_timer_update_irqs(s);
137df41cbd6SAlistair Francis }
138df41cbd6SAlistair Francis 
139df41cbd6SAlistair Francis static uint64_t ibex_timer_read(void *opaque, hwaddr addr,
140df41cbd6SAlistair Francis                                        unsigned int size)
141df41cbd6SAlistair Francis {
142df41cbd6SAlistair Francis     IbexTimerState *s = opaque;
143df41cbd6SAlistair Francis     uint64_t now = cpu_riscv_read_rtc(s->timebase_freq);
144df41cbd6SAlistair Francis     uint64_t retvalue = 0;
145df41cbd6SAlistair Francis 
146df41cbd6SAlistair Francis     switch (addr >> 2) {
147*dda94e5cSWilfred Mallawa     case R_ALERT_TEST:
148*dda94e5cSWilfred Mallawa         qemu_log_mask(LOG_GUEST_ERROR,
149*dda94e5cSWilfred Mallawa                         "Attempted to read ALERT_TEST, a write only register");
150*dda94e5cSWilfred Mallawa         break;
151df41cbd6SAlistair Francis     case R_CTRL:
152df41cbd6SAlistair Francis         retvalue = s->timer_ctrl;
153df41cbd6SAlistair Francis         break;
154df41cbd6SAlistair Francis     case R_CFG0:
155df41cbd6SAlistair Francis         retvalue = s->timer_cfg0;
156df41cbd6SAlistair Francis         break;
157df41cbd6SAlistair Francis     case R_LOWER0:
158df41cbd6SAlistair Francis         retvalue = now;
159df41cbd6SAlistair Francis         break;
160df41cbd6SAlistair Francis     case R_UPPER0:
161df41cbd6SAlistair Francis         retvalue = now >> 32;
162df41cbd6SAlistair Francis         break;
163df41cbd6SAlistair Francis     case R_COMPARE_LOWER0:
164df41cbd6SAlistair Francis         retvalue = s->timer_compare_lower0;
165df41cbd6SAlistair Francis         break;
166df41cbd6SAlistair Francis     case R_COMPARE_UPPER0:
167df41cbd6SAlistair Francis         retvalue = s->timer_compare_upper0;
168df41cbd6SAlistair Francis         break;
169df41cbd6SAlistair Francis     case R_INTR_ENABLE:
170df41cbd6SAlistair Francis         retvalue = s->timer_intr_enable;
171df41cbd6SAlistair Francis         break;
172df41cbd6SAlistair Francis     case R_INTR_STATE:
173df41cbd6SAlistair Francis         retvalue = s->timer_intr_state;
174df41cbd6SAlistair Francis         break;
175df41cbd6SAlistair Francis     case R_INTR_TEST:
17628ca4689SWilfred Mallawa         qemu_log_mask(LOG_GUEST_ERROR,
17728ca4689SWilfred Mallawa                       "Attempted to read INTR_TEST, a write only register");
178df41cbd6SAlistair Francis         break;
179df41cbd6SAlistair Francis     default:
180df41cbd6SAlistair Francis         qemu_log_mask(LOG_GUEST_ERROR,
181df41cbd6SAlistair Francis                       "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
182df41cbd6SAlistair Francis         return 0;
183df41cbd6SAlistair Francis     }
184df41cbd6SAlistair Francis 
185df41cbd6SAlistair Francis     return retvalue;
186df41cbd6SAlistair Francis }
187df41cbd6SAlistair Francis 
188df41cbd6SAlistair Francis static void ibex_timer_write(void *opaque, hwaddr addr,
189df41cbd6SAlistair Francis                              uint64_t val64, unsigned int size)
190df41cbd6SAlistair Francis {
191df41cbd6SAlistair Francis     IbexTimerState *s = opaque;
192df41cbd6SAlistair Francis     uint32_t val = val64;
193df41cbd6SAlistair Francis 
194df41cbd6SAlistair Francis     switch (addr >> 2) {
195*dda94e5cSWilfred Mallawa     case R_ALERT_TEST:
196*dda94e5cSWilfred Mallawa         qemu_log_mask(LOG_UNIMP, "Alert triggering not supported");
197*dda94e5cSWilfred Mallawa         break;
198df41cbd6SAlistair Francis     case R_CTRL:
199df41cbd6SAlistair Francis         s->timer_ctrl = val;
200df41cbd6SAlistair Francis         break;
201df41cbd6SAlistair Francis     case R_CFG0:
202df41cbd6SAlistair Francis         qemu_log_mask(LOG_UNIMP, "Changing prescale or step not supported");
203df41cbd6SAlistair Francis         s->timer_cfg0 = val;
204df41cbd6SAlistair Francis         break;
205df41cbd6SAlistair Francis     case R_LOWER0:
206df41cbd6SAlistair Francis         qemu_log_mask(LOG_UNIMP, "Changing timer value is not supported");
207df41cbd6SAlistair Francis         break;
208df41cbd6SAlistair Francis     case R_UPPER0:
209df41cbd6SAlistair Francis         qemu_log_mask(LOG_UNIMP, "Changing timer value is not supported");
210df41cbd6SAlistair Francis         break;
211df41cbd6SAlistair Francis     case R_COMPARE_LOWER0:
212df41cbd6SAlistair Francis         s->timer_compare_lower0 = val;
213df41cbd6SAlistair Francis         ibex_timer_update_irqs(s);
214df41cbd6SAlistair Francis         break;
215df41cbd6SAlistair Francis     case R_COMPARE_UPPER0:
216df41cbd6SAlistair Francis         s->timer_compare_upper0 = val;
217df41cbd6SAlistair Francis         ibex_timer_update_irqs(s);
218df41cbd6SAlistair Francis         break;
219df41cbd6SAlistair Francis     case R_INTR_ENABLE:
220df41cbd6SAlistair Francis         s->timer_intr_enable = val;
221df41cbd6SAlistair Francis         break;
222df41cbd6SAlistair Francis     case R_INTR_STATE:
223df41cbd6SAlistair Francis         /* Write 1 to clear */
224df41cbd6SAlistair Francis         s->timer_intr_state &= ~val;
225df41cbd6SAlistair Francis         break;
226df41cbd6SAlistair Francis     case R_INTR_TEST:
22728ca4689SWilfred Mallawa         if (s->timer_intr_enable & val & R_INTR_ENABLE_IE_0_MASK) {
228df41cbd6SAlistair Francis             s->timer_intr_state |= R_INTR_STATE_IS_0_MASK;
229df41cbd6SAlistair Francis             qemu_set_irq(s->irq, true);
230df41cbd6SAlistair Francis         }
231df41cbd6SAlistair Francis         break;
232df41cbd6SAlistair Francis     default:
233df41cbd6SAlistair Francis         qemu_log_mask(LOG_GUEST_ERROR,
234df41cbd6SAlistair Francis                       "%s: Bad offset 0x%"HWADDR_PRIx"\n", __func__, addr);
235df41cbd6SAlistair Francis     }
236df41cbd6SAlistair Francis }
237df41cbd6SAlistair Francis 
238df41cbd6SAlistair Francis static const MemoryRegionOps ibex_timer_ops = {
239df41cbd6SAlistair Francis     .read = ibex_timer_read,
240df41cbd6SAlistair Francis     .write = ibex_timer_write,
241df41cbd6SAlistair Francis     .endianness = DEVICE_NATIVE_ENDIAN,
242df41cbd6SAlistair Francis     .impl.min_access_size = 4,
243df41cbd6SAlistair Francis     .impl.max_access_size = 4,
244df41cbd6SAlistair Francis };
245df41cbd6SAlistair Francis 
246df41cbd6SAlistair Francis static int ibex_timer_post_load(void *opaque, int version_id)
247df41cbd6SAlistair Francis {
248df41cbd6SAlistair Francis     IbexTimerState *s = opaque;
249df41cbd6SAlistair Francis 
250df41cbd6SAlistair Francis     ibex_timer_update_irqs(s);
251df41cbd6SAlistair Francis     return 0;
252df41cbd6SAlistair Francis }
253df41cbd6SAlistair Francis 
254df41cbd6SAlistair Francis static const VMStateDescription vmstate_ibex_timer = {
255df41cbd6SAlistair Francis     .name = TYPE_IBEX_TIMER,
25628ca4689SWilfred Mallawa     .version_id = 2,
25728ca4689SWilfred Mallawa     .minimum_version_id = 2,
258df41cbd6SAlistair Francis     .post_load = ibex_timer_post_load,
259df41cbd6SAlistair Francis     .fields = (VMStateField[]) {
260df41cbd6SAlistair Francis         VMSTATE_UINT32(timer_ctrl, IbexTimerState),
261df41cbd6SAlistair Francis         VMSTATE_UINT32(timer_cfg0, IbexTimerState),
262df41cbd6SAlistair Francis         VMSTATE_UINT32(timer_compare_lower0, IbexTimerState),
263df41cbd6SAlistair Francis         VMSTATE_UINT32(timer_compare_upper0, IbexTimerState),
264df41cbd6SAlistair Francis         VMSTATE_UINT32(timer_intr_enable, IbexTimerState),
265df41cbd6SAlistair Francis         VMSTATE_UINT32(timer_intr_state, IbexTimerState),
266df41cbd6SAlistair Francis         VMSTATE_END_OF_LIST()
267df41cbd6SAlistair Francis     }
268df41cbd6SAlistair Francis };
269df41cbd6SAlistair Francis 
270df41cbd6SAlistair Francis static Property ibex_timer_properties[] = {
271df41cbd6SAlistair Francis     DEFINE_PROP_UINT32("timebase-freq", IbexTimerState, timebase_freq, 10000),
272df41cbd6SAlistair Francis     DEFINE_PROP_END_OF_LIST(),
273df41cbd6SAlistair Francis };
274df41cbd6SAlistair Francis 
275df41cbd6SAlistair Francis static void ibex_timer_init(Object *obj)
276df41cbd6SAlistair Francis {
277df41cbd6SAlistair Francis     IbexTimerState *s = IBEX_TIMER(obj);
278df41cbd6SAlistair Francis 
279df41cbd6SAlistair Francis     sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
280df41cbd6SAlistair Francis 
281df41cbd6SAlistair Francis     memory_region_init_io(&s->mmio, obj, &ibex_timer_ops, s,
282df41cbd6SAlistair Francis                           TYPE_IBEX_TIMER, 0x400);
283df41cbd6SAlistair Francis     sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmio);
284df41cbd6SAlistair Francis }
285df41cbd6SAlistair Francis 
28657a3a622SAlistair Francis static void ibex_timer_realize(DeviceState *dev, Error **errp)
28757a3a622SAlistair Francis {
28857a3a622SAlistair Francis     IbexTimerState *s = IBEX_TIMER(dev);
28957a3a622SAlistair Francis 
29057a3a622SAlistair Francis     qdev_init_gpio_out(dev, &s->m_timer_irq, 1);
29157a3a622SAlistair Francis }
29257a3a622SAlistair Francis 
29357a3a622SAlistair Francis 
294df41cbd6SAlistair Francis static void ibex_timer_class_init(ObjectClass *klass, void *data)
295df41cbd6SAlistair Francis {
296df41cbd6SAlistair Francis     DeviceClass *dc = DEVICE_CLASS(klass);
297df41cbd6SAlistair Francis 
298df41cbd6SAlistair Francis     dc->reset = ibex_timer_reset;
299df41cbd6SAlistair Francis     dc->vmsd = &vmstate_ibex_timer;
30057a3a622SAlistair Francis     dc->realize = ibex_timer_realize;
301df41cbd6SAlistair Francis     device_class_set_props(dc, ibex_timer_properties);
302df41cbd6SAlistair Francis }
303df41cbd6SAlistair Francis 
304df41cbd6SAlistair Francis static const TypeInfo ibex_timer_info = {
305df41cbd6SAlistair Francis     .name          = TYPE_IBEX_TIMER,
306df41cbd6SAlistair Francis     .parent        = TYPE_SYS_BUS_DEVICE,
307df41cbd6SAlistair Francis     .instance_size = sizeof(IbexTimerState),
308df41cbd6SAlistair Francis     .instance_init = ibex_timer_init,
309df41cbd6SAlistair Francis     .class_init    = ibex_timer_class_init,
310df41cbd6SAlistair Francis };
311df41cbd6SAlistair Francis 
312df41cbd6SAlistair Francis static void ibex_timer_register_types(void)
313df41cbd6SAlistair Francis {
314df41cbd6SAlistair Francis     type_register_static(&ibex_timer_info);
315df41cbd6SAlistair Francis }
316df41cbd6SAlistair Francis 
317df41cbd6SAlistair Francis type_init(ibex_timer_register_types)
318