blob: 40728e85e207f59c92ebacb4ddb344a9a9f1c8b0 [file] [log] [blame]
Peter Maydell5dd85b42017-07-17 13:36:08 +01001/*
2 * ARM CMSDK APB timer emulation
3 *
4 * Copyright (c) 2017 Linaro Limited
5 * Written by Peter Maydell
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2 or
9 * (at your option) any later version.
10 */
11
12/* This is a model of the "APB timer" which is part of the Cortex-M
13 * System Design Kit (CMSDK) and documented in the Cortex-M System
14 * Design Kit Technical Reference Manual (ARM DDI0479C):
15 * https://developer.arm.com/products/system-design/system-design-kits/cortex-m-system-design-kit
16 *
17 * The hardware has an EXTIN input wire, which can be configured
18 * by the guest to act either as a 'timer enable' (timer does not run
19 * when EXTIN is low), or as a 'timer clock' (timer runs at frequency
20 * of EXTIN clock, not PCLK frequency). We don't model this.
21 *
22 * The documentation is not very clear about the exact behaviour;
23 * we choose to implement that the interrupt is triggered when
24 * the counter goes from 1 to 0, that the counter then holds at 0
25 * for one clock cycle before reloading from the RELOAD register,
26 * and that if the RELOAD register is 0 this does not cause an
27 * interrupt (as there is no further 1->0 transition).
28 */
29
30#include "qemu/osdep.h"
31#include "qemu/log.h"
Markus Armbruster0b8fa322019-05-23 16:35:07 +020032#include "qemu/module.h"
Peter Maydell5dd85b42017-07-17 13:36:08 +010033#include "qapi/error.h"
34#include "trace.h"
35#include "hw/sysbus.h"
Markus Armbruster64552b62019-08-12 07:23:42 +020036#include "hw/irq.h"
Peter Maydell5dd85b42017-07-17 13:36:08 +010037#include "hw/registerfields.h"
38#include "hw/timer/cmsdk-apb-timer.h"
Markus Armbrusterd6454272019-08-12 07:23:45 +020039#include "migration/vmstate.h"
Peter Maydell5dd85b42017-07-17 13:36:08 +010040
41REG32(CTRL, 0)
42 FIELD(CTRL, EN, 0, 1)
43 FIELD(CTRL, SELEXTEN, 1, 1)
44 FIELD(CTRL, SELEXTCLK, 2, 1)
45 FIELD(CTRL, IRQEN, 3, 1)
46REG32(VALUE, 4)
47REG32(RELOAD, 8)
48REG32(INTSTATUS, 0xc)
49 FIELD(INTSTATUS, IRQ, 0, 1)
50REG32(PID4, 0xFD0)
51REG32(PID5, 0xFD4)
52REG32(PID6, 0xFD8)
53REG32(PID7, 0xFDC)
54REG32(PID0, 0xFE0)
55REG32(PID1, 0xFE4)
56REG32(PID2, 0xFE8)
57REG32(PID3, 0xFEC)
58REG32(CID0, 0xFF0)
59REG32(CID1, 0xFF4)
60REG32(CID2, 0xFF8)
61REG32(CID3, 0xFFC)
62
63/* PID/CID values */
64static const int timer_id[] = {
65 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */
66 0x22, 0xb8, 0x1b, 0x00, /* PID0..PID3 */
67 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */
68};
69
70static void cmsdk_apb_timer_update(CMSDKAPBTIMER *s)
71{
72 qemu_set_irq(s->timerint, !!(s->intstatus & R_INTSTATUS_IRQ_MASK));
73}
74
75static uint64_t cmsdk_apb_timer_read(void *opaque, hwaddr offset, unsigned size)
76{
77 CMSDKAPBTIMER *s = CMSDK_APB_TIMER(opaque);
78 uint64_t r;
79
80 switch (offset) {
81 case A_CTRL:
82 r = s->ctrl;
83 break;
84 case A_VALUE:
85 r = ptimer_get_count(s->timer);
86 break;
87 case A_RELOAD:
88 r = ptimer_get_limit(s->timer);
89 break;
90 case A_INTSTATUS:
91 r = s->intstatus;
92 break;
93 case A_PID4 ... A_CID3:
94 r = timer_id[(offset - A_PID4) / 4];
95 break;
96 default:
97 qemu_log_mask(LOG_GUEST_ERROR,
98 "CMSDK APB timer read: bad offset %x\n", (int) offset);
99 r = 0;
100 break;
101 }
102 trace_cmsdk_apb_timer_read(offset, r, size);
103 return r;
104}
105
106static void cmsdk_apb_timer_write(void *opaque, hwaddr offset, uint64_t value,
107 unsigned size)
108{
109 CMSDKAPBTIMER *s = CMSDK_APB_TIMER(opaque);
110
111 trace_cmsdk_apb_timer_write(offset, value, size);
112
113 switch (offset) {
114 case A_CTRL:
115 if (value & 6) {
116 /* Bits [1] and [2] enable using EXTIN as either clock or
117 * an enable line. We don't model this.
118 */
119 qemu_log_mask(LOG_UNIMP,
120 "CMSDK APB timer: EXTIN input not supported\n");
121 }
122 s->ctrl = value & 0xf;
Peter Maydell19c12fe2019-10-08 18:17:28 +0100123 ptimer_transaction_begin(s->timer);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100124 if (s->ctrl & R_CTRL_EN_MASK) {
Guenter Roeck0e256832018-07-09 14:51:34 +0100125 ptimer_run(s->timer, ptimer_get_limit(s->timer) == 0);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100126 } else {
127 ptimer_stop(s->timer);
128 }
Peter Maydell19c12fe2019-10-08 18:17:28 +0100129 ptimer_transaction_commit(s->timer);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100130 break;
131 case A_RELOAD:
132 /* Writing to reload also sets the current timer value */
Peter Maydell19c12fe2019-10-08 18:17:28 +0100133 ptimer_transaction_begin(s->timer);
Peter Maydell1a9b3062018-07-09 14:51:34 +0100134 if (!value) {
135 ptimer_stop(s->timer);
136 }
Peter Maydell5dd85b42017-07-17 13:36:08 +0100137 ptimer_set_limit(s->timer, value, 1);
Peter Maydell1a9b3062018-07-09 14:51:34 +0100138 if (value && (s->ctrl & R_CTRL_EN_MASK)) {
139 /*
140 * Make sure timer is running (it might have stopped if this
141 * was an expired one-shot timer)
142 */
143 ptimer_run(s->timer, 0);
144 }
Peter Maydell19c12fe2019-10-08 18:17:28 +0100145 ptimer_transaction_commit(s->timer);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100146 break;
147 case A_VALUE:
Peter Maydell19c12fe2019-10-08 18:17:28 +0100148 ptimer_transaction_begin(s->timer);
Peter Maydell1a9b3062018-07-09 14:51:34 +0100149 if (!value && !ptimer_get_limit(s->timer)) {
150 ptimer_stop(s->timer);
151 }
Peter Maydell5dd85b42017-07-17 13:36:08 +0100152 ptimer_set_count(s->timer, value);
Peter Maydell1a9b3062018-07-09 14:51:34 +0100153 if (value && (s->ctrl & R_CTRL_EN_MASK)) {
154 ptimer_run(s->timer, ptimer_get_limit(s->timer) == 0);
155 }
Peter Maydell19c12fe2019-10-08 18:17:28 +0100156 ptimer_transaction_commit(s->timer);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100157 break;
158 case A_INTSTATUS:
159 /* Just one bit, which is W1C. */
160 value &= 1;
161 s->intstatus &= ~value;
162 cmsdk_apb_timer_update(s);
163 break;
164 case A_PID4 ... A_CID3:
165 qemu_log_mask(LOG_GUEST_ERROR,
166 "CMSDK APB timer write: write to RO offset 0x%x\n",
167 (int)offset);
168 break;
169 default:
170 qemu_log_mask(LOG_GUEST_ERROR,
171 "CMSDK APB timer write: bad offset 0x%x\n", (int) offset);
172 break;
173 }
174}
175
176static const MemoryRegionOps cmsdk_apb_timer_ops = {
177 .read = cmsdk_apb_timer_read,
178 .write = cmsdk_apb_timer_write,
179 .endianness = DEVICE_LITTLE_ENDIAN,
180};
181
182static void cmsdk_apb_timer_tick(void *opaque)
183{
184 CMSDKAPBTIMER *s = CMSDK_APB_TIMER(opaque);
185
186 if (s->ctrl & R_CTRL_IRQEN_MASK) {
187 s->intstatus |= R_INTSTATUS_IRQ_MASK;
188 cmsdk_apb_timer_update(s);
189 }
190}
191
192static void cmsdk_apb_timer_reset(DeviceState *dev)
193{
194 CMSDKAPBTIMER *s = CMSDK_APB_TIMER(dev);
195
196 trace_cmsdk_apb_timer_reset();
197 s->ctrl = 0;
198 s->intstatus = 0;
Peter Maydell19c12fe2019-10-08 18:17:28 +0100199 ptimer_transaction_begin(s->timer);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100200 ptimer_stop(s->timer);
201 /* Set the limit and the count */
202 ptimer_set_limit(s->timer, 0, 1);
Peter Maydell19c12fe2019-10-08 18:17:28 +0100203 ptimer_transaction_commit(s->timer);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100204}
205
206static void cmsdk_apb_timer_init(Object *obj)
207{
208 SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
209 CMSDKAPBTIMER *s = CMSDK_APB_TIMER(obj);
210
211 memory_region_init_io(&s->iomem, obj, &cmsdk_apb_timer_ops,
212 s, "cmsdk-apb-timer", 0x1000);
213 sysbus_init_mmio(sbd, &s->iomem);
214 sysbus_init_irq(sbd, &s->timerint);
215}
216
217static void cmsdk_apb_timer_realize(DeviceState *dev, Error **errp)
218{
219 CMSDKAPBTIMER *s = CMSDK_APB_TIMER(dev);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100220
221 if (s->pclk_frq == 0) {
222 error_setg(errp, "CMSDK APB timer: pclk-frq property must be set");
223 return;
224 }
225
Peter Maydell19c12fe2019-10-08 18:17:28 +0100226 s->timer = ptimer_init(cmsdk_apb_timer_tick, s,
Peter Maydell5dd85b42017-07-17 13:36:08 +0100227 PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD |
Peter Maydell65830802018-07-09 14:51:34 +0100228 PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT |
Peter Maydell5dd85b42017-07-17 13:36:08 +0100229 PTIMER_POLICY_NO_IMMEDIATE_RELOAD |
230 PTIMER_POLICY_NO_COUNTER_ROUND_DOWN);
231
Peter Maydell19c12fe2019-10-08 18:17:28 +0100232 ptimer_transaction_begin(s->timer);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100233 ptimer_set_freq(s->timer, s->pclk_frq);
Peter Maydell19c12fe2019-10-08 18:17:28 +0100234 ptimer_transaction_commit(s->timer);
Peter Maydell5dd85b42017-07-17 13:36:08 +0100235}
236
237static const VMStateDescription cmsdk_apb_timer_vmstate = {
238 .name = "cmsdk-apb-timer",
239 .version_id = 1,
240 .minimum_version_id = 1,
241 .fields = (VMStateField[]) {
242 VMSTATE_PTIMER(timer, CMSDKAPBTIMER),
243 VMSTATE_UINT32(ctrl, CMSDKAPBTIMER),
244 VMSTATE_UINT32(value, CMSDKAPBTIMER),
245 VMSTATE_UINT32(reload, CMSDKAPBTIMER),
246 VMSTATE_UINT32(intstatus, CMSDKAPBTIMER),
247 VMSTATE_END_OF_LIST()
248 }
249};
250
251static Property cmsdk_apb_timer_properties[] = {
252 DEFINE_PROP_UINT32("pclk-frq", CMSDKAPBTIMER, pclk_frq, 0),
253 DEFINE_PROP_END_OF_LIST(),
254};
255
256static void cmsdk_apb_timer_class_init(ObjectClass *klass, void *data)
257{
258 DeviceClass *dc = DEVICE_CLASS(klass);
259
260 dc->realize = cmsdk_apb_timer_realize;
261 dc->vmsd = &cmsdk_apb_timer_vmstate;
262 dc->reset = cmsdk_apb_timer_reset;
263 dc->props = cmsdk_apb_timer_properties;
264}
265
266static const TypeInfo cmsdk_apb_timer_info = {
267 .name = TYPE_CMSDK_APB_TIMER,
268 .parent = TYPE_SYS_BUS_DEVICE,
269 .instance_size = sizeof(CMSDKAPBTIMER),
270 .instance_init = cmsdk_apb_timer_init,
271 .class_init = cmsdk_apb_timer_class_init,
272};
273
274static void cmsdk_apb_timer_register_types(void)
275{
276 type_register_static(&cmsdk_apb_timer_info);
277}
278
279type_init(cmsdk_apb_timer_register_types);