1 | /* |
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" |
32 | #include "qemu/main-loop.h" |
33 | #include "qemu/module.h" |
34 | #include "qapi/error.h" |
35 | #include "trace.h" |
36 | #include "hw/sysbus.h" |
37 | #include "hw/irq.h" |
38 | #include "hw/registerfields.h" |
39 | #include "hw/timer/cmsdk-apb-timer.h" |
40 | #include "migration/vmstate.h" |
41 | |
42 | REG32(CTRL, 0) |
43 | FIELD(CTRL, EN, 0, 1) |
44 | FIELD(CTRL, SELEXTEN, 1, 1) |
45 | FIELD(CTRL, SELEXTCLK, 2, 1) |
46 | FIELD(CTRL, IRQEN, 3, 1) |
47 | REG32(VALUE, 4) |
48 | REG32(RELOAD, 8) |
49 | REG32(INTSTATUS, 0xc) |
50 | FIELD(INTSTATUS, IRQ, 0, 1) |
51 | REG32(PID4, 0xFD0) |
52 | REG32(PID5, 0xFD4) |
53 | REG32(PID6, 0xFD8) |
54 | REG32(PID7, 0xFDC) |
55 | REG32(PID0, 0xFE0) |
56 | REG32(PID1, 0xFE4) |
57 | REG32(PID2, 0xFE8) |
58 | REG32(PID3, 0xFEC) |
59 | REG32(CID0, 0xFF0) |
60 | REG32(CID1, 0xFF4) |
61 | REG32(CID2, 0xFF8) |
62 | REG32(CID3, 0xFFC) |
63 | |
64 | /* PID/CID values */ |
65 | static const int timer_id[] = { |
66 | 0x04, 0x00, 0x00, 0x00, /* PID4..PID7 */ |
67 | 0x22, 0xb8, 0x1b, 0x00, /* PID0..PID3 */ |
68 | 0x0d, 0xf0, 0x05, 0xb1, /* CID0..CID3 */ |
69 | }; |
70 | |
71 | static void cmsdk_apb_timer_update(CMSDKAPBTIMER *s) |
72 | { |
73 | qemu_set_irq(s->timerint, !!(s->intstatus & R_INTSTATUS_IRQ_MASK)); |
74 | } |
75 | |
76 | static uint64_t cmsdk_apb_timer_read(void *opaque, hwaddr offset, unsigned size) |
77 | { |
78 | CMSDKAPBTIMER *s = CMSDK_APB_TIMER(opaque); |
79 | uint64_t r; |
80 | |
81 | switch (offset) { |
82 | case A_CTRL: |
83 | r = s->ctrl; |
84 | break; |
85 | case A_VALUE: |
86 | r = ptimer_get_count(s->timer); |
87 | break; |
88 | case A_RELOAD: |
89 | r = ptimer_get_limit(s->timer); |
90 | break; |
91 | case A_INTSTATUS: |
92 | r = s->intstatus; |
93 | break; |
94 | case A_PID4 ... A_CID3: |
95 | r = timer_id[(offset - A_PID4) / 4]; |
96 | break; |
97 | default: |
98 | qemu_log_mask(LOG_GUEST_ERROR, |
99 | "CMSDK APB timer read: bad offset %x\n" , (int) offset); |
100 | r = 0; |
101 | break; |
102 | } |
103 | trace_cmsdk_apb_timer_read(offset, r, size); |
104 | return r; |
105 | } |
106 | |
107 | static void cmsdk_apb_timer_write(void *opaque, hwaddr offset, uint64_t value, |
108 | unsigned size) |
109 | { |
110 | CMSDKAPBTIMER *s = CMSDK_APB_TIMER(opaque); |
111 | |
112 | trace_cmsdk_apb_timer_write(offset, value, size); |
113 | |
114 | switch (offset) { |
115 | case A_CTRL: |
116 | if (value & 6) { |
117 | /* Bits [1] and [2] enable using EXTIN as either clock or |
118 | * an enable line. We don't model this. |
119 | */ |
120 | qemu_log_mask(LOG_UNIMP, |
121 | "CMSDK APB timer: EXTIN input not supported\n" ); |
122 | } |
123 | s->ctrl = value & 0xf; |
124 | if (s->ctrl & R_CTRL_EN_MASK) { |
125 | ptimer_run(s->timer, ptimer_get_limit(s->timer) == 0); |
126 | } else { |
127 | ptimer_stop(s->timer); |
128 | } |
129 | break; |
130 | case A_RELOAD: |
131 | /* Writing to reload also sets the current timer value */ |
132 | if (!value) { |
133 | ptimer_stop(s->timer); |
134 | } |
135 | ptimer_set_limit(s->timer, value, 1); |
136 | if (value && (s->ctrl & R_CTRL_EN_MASK)) { |
137 | /* |
138 | * Make sure timer is running (it might have stopped if this |
139 | * was an expired one-shot timer) |
140 | */ |
141 | ptimer_run(s->timer, 0); |
142 | } |
143 | break; |
144 | case A_VALUE: |
145 | if (!value && !ptimer_get_limit(s->timer)) { |
146 | ptimer_stop(s->timer); |
147 | } |
148 | ptimer_set_count(s->timer, value); |
149 | if (value && (s->ctrl & R_CTRL_EN_MASK)) { |
150 | ptimer_run(s->timer, ptimer_get_limit(s->timer) == 0); |
151 | } |
152 | break; |
153 | case A_INTSTATUS: |
154 | /* Just one bit, which is W1C. */ |
155 | value &= 1; |
156 | s->intstatus &= ~value; |
157 | cmsdk_apb_timer_update(s); |
158 | break; |
159 | case A_PID4 ... A_CID3: |
160 | qemu_log_mask(LOG_GUEST_ERROR, |
161 | "CMSDK APB timer write: write to RO offset 0x%x\n" , |
162 | (int)offset); |
163 | break; |
164 | default: |
165 | qemu_log_mask(LOG_GUEST_ERROR, |
166 | "CMSDK APB timer write: bad offset 0x%x\n" , (int) offset); |
167 | break; |
168 | } |
169 | } |
170 | |
171 | static const MemoryRegionOps cmsdk_apb_timer_ops = { |
172 | .read = cmsdk_apb_timer_read, |
173 | .write = cmsdk_apb_timer_write, |
174 | .endianness = DEVICE_LITTLE_ENDIAN, |
175 | }; |
176 | |
177 | static void cmsdk_apb_timer_tick(void *opaque) |
178 | { |
179 | CMSDKAPBTIMER *s = CMSDK_APB_TIMER(opaque); |
180 | |
181 | if (s->ctrl & R_CTRL_IRQEN_MASK) { |
182 | s->intstatus |= R_INTSTATUS_IRQ_MASK; |
183 | cmsdk_apb_timer_update(s); |
184 | } |
185 | } |
186 | |
187 | static void cmsdk_apb_timer_reset(DeviceState *dev) |
188 | { |
189 | CMSDKAPBTIMER *s = CMSDK_APB_TIMER(dev); |
190 | |
191 | trace_cmsdk_apb_timer_reset(); |
192 | s->ctrl = 0; |
193 | s->intstatus = 0; |
194 | ptimer_stop(s->timer); |
195 | /* Set the limit and the count */ |
196 | ptimer_set_limit(s->timer, 0, 1); |
197 | } |
198 | |
199 | static void cmsdk_apb_timer_init(Object *obj) |
200 | { |
201 | SysBusDevice *sbd = SYS_BUS_DEVICE(obj); |
202 | CMSDKAPBTIMER *s = CMSDK_APB_TIMER(obj); |
203 | |
204 | memory_region_init_io(&s->iomem, obj, &cmsdk_apb_timer_ops, |
205 | s, "cmsdk-apb-timer" , 0x1000); |
206 | sysbus_init_mmio(sbd, &s->iomem); |
207 | sysbus_init_irq(sbd, &s->timerint); |
208 | } |
209 | |
210 | static void cmsdk_apb_timer_realize(DeviceState *dev, Error **errp) |
211 | { |
212 | CMSDKAPBTIMER *s = CMSDK_APB_TIMER(dev); |
213 | QEMUBH *bh; |
214 | |
215 | if (s->pclk_frq == 0) { |
216 | error_setg(errp, "CMSDK APB timer: pclk-frq property must be set" ); |
217 | return; |
218 | } |
219 | |
220 | bh = qemu_bh_new(cmsdk_apb_timer_tick, s); |
221 | s->timer = ptimer_init(bh, |
222 | PTIMER_POLICY_WRAP_AFTER_ONE_PERIOD | |
223 | PTIMER_POLICY_TRIGGER_ONLY_ON_DECREMENT | |
224 | PTIMER_POLICY_NO_IMMEDIATE_RELOAD | |
225 | PTIMER_POLICY_NO_COUNTER_ROUND_DOWN); |
226 | |
227 | ptimer_set_freq(s->timer, s->pclk_frq); |
228 | } |
229 | |
230 | static const VMStateDescription cmsdk_apb_timer_vmstate = { |
231 | .name = "cmsdk-apb-timer" , |
232 | .version_id = 1, |
233 | .minimum_version_id = 1, |
234 | .fields = (VMStateField[]) { |
235 | VMSTATE_PTIMER(timer, CMSDKAPBTIMER), |
236 | VMSTATE_UINT32(ctrl, CMSDKAPBTIMER), |
237 | VMSTATE_UINT32(value, CMSDKAPBTIMER), |
238 | VMSTATE_UINT32(reload, CMSDKAPBTIMER), |
239 | VMSTATE_UINT32(intstatus, CMSDKAPBTIMER), |
240 | VMSTATE_END_OF_LIST() |
241 | } |
242 | }; |
243 | |
244 | static Property cmsdk_apb_timer_properties[] = { |
245 | DEFINE_PROP_UINT32("pclk-frq" , CMSDKAPBTIMER, pclk_frq, 0), |
246 | DEFINE_PROP_END_OF_LIST(), |
247 | }; |
248 | |
249 | static void cmsdk_apb_timer_class_init(ObjectClass *klass, void *data) |
250 | { |
251 | DeviceClass *dc = DEVICE_CLASS(klass); |
252 | |
253 | dc->realize = cmsdk_apb_timer_realize; |
254 | dc->vmsd = &cmsdk_apb_timer_vmstate; |
255 | dc->reset = cmsdk_apb_timer_reset; |
256 | dc->props = cmsdk_apb_timer_properties; |
257 | } |
258 | |
259 | static const TypeInfo cmsdk_apb_timer_info = { |
260 | .name = TYPE_CMSDK_APB_TIMER, |
261 | .parent = TYPE_SYS_BUS_DEVICE, |
262 | .instance_size = sizeof(CMSDKAPBTIMER), |
263 | .instance_init = cmsdk_apb_timer_init, |
264 | .class_init = cmsdk_apb_timer_class_init, |
265 | }; |
266 | |
267 | static void cmsdk_apb_timer_register_types(void) |
268 | { |
269 | type_register_static(&cmsdk_apb_timer_info); |
270 | } |
271 | |
272 | type_init(cmsdk_apb_timer_register_types); |
273 | |